{"id":48250152,"url":"https://github.com/sivaosorg/replify","last_synced_at":"2026-04-04T20:49:28.778Z","repository":{"id":331642757,"uuid":"1129086707","full_name":"sivaosorg/replify","owner":"sivaosorg","description":"replify · A Go library to simplify and standardize API response wrapping for RESTful services.","archived":false,"fork":false,"pushed_at":"2026-03-30T15:23:32.000Z","size":1132,"stargazers_count":0,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-03-30T17:31:42.002Z","etag":null,"topics":["go","go-library","go-pkg","golang","golang-pkg","golang-rest","golang-rest-api","http","http-status-codes","replify","response-codes","response-wrapper","restful-api","utilities"],"latest_commit_sha":null,"homepage":"https://github.com/sivaosorg/replify/wiki/Getting-Started","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sivaosorg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-06T15:31:13.000Z","updated_at":"2026-03-30T15:25:53.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/sivaosorg/replify","commit_stats":null,"previous_names":["sivaosorg/replify"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/sivaosorg/replify","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sivaosorg%2Freplify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sivaosorg%2Freplify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sivaosorg%2Freplify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sivaosorg%2Freplify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sivaosorg","download_url":"https://codeload.github.com/sivaosorg/replify/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sivaosorg%2Freplify/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31413284,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T20:09:54.854Z","status":"ssl_error","status_checked_at":"2026-04-04T20:09:44.350Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","go-library","go-pkg","golang","golang-pkg","golang-rest","golang-rest-api","http","http-status-codes","replify","response-codes","response-wrapper","restful-api","utilities"],"created_at":"2026-04-04T20:49:27.912Z","updated_at":"2026-04-04T20:49:28.764Z","avatar_url":"https://github.com/sivaosorg.png","language":"Go","readme":"# replify\n\n**replify** is a Go library designed to simplify and standardize API response wrapping for RESTful services. It leverages the Decorator Pattern to dynamically add error handling, metadata, pagination, and other response features in a clean and human-readable format.\n\n[![Go Version](https://img.shields.io/badge/Go-%3E%3D%201.23-blue)](https://go.dev/)\n[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)\n\n## Overview\n\nBuilding RESTful APIs often requires repetitive boilerplate code for standardizing responses. **replify** eliminates this by providing a fluent, chainable API that ensures consistent response formats across all your endpoints.\n\n### What Problems Does It Solve?\n\n- ❌ **Inconsistent response formats** across different endpoints\n- ❌ **Repetitive error handling** boilerplate in every handler\n- ❌ **Manual metadata management** (request IDs, timestamps, versions)\n- ❌ **Complex pagination logic** scattered throughout the codebase\n- ❌ **Debugging difficulties** in production vs development environments\n\n### The Solution\n\n✅ **Standardized response structure** - One format for all endpoints  \n✅ **Fluent API** - Chainable methods for building responses  \n✅ **Built-in pagination** - Complete pagination support out of the box  \n✅ **Metadata management** - Request IDs, timestamps, API versions, locales  \n✅ **Conditional debugging** - Development-only debug information  \n✅ **Error handling** - Stack traces, error wrapping, contextual messages  \n✅ **Type safety** - Full type safety with Go generics  \n✅ **Zero dependencies** - Only uses Go standard library\n\n## Features\n\n### Core Capabilities\n\n- 🎯 **Standardized JSON Format** - Consistent structure across all API responses\n- 🔗 **Fluent Builder Pattern** - Chain methods to construct complex responses\n- 📄 **Pagination Support** - Built-in page, per_page, total_items, total_pages, is_last\n- 🔍 **Request Tracing** - Track requests with unique IDs across microservices\n- 🌍 **Internationalization** - Locale support for multi-language APIs\n- 🐛 **Debug Mode** - Conditional debugging information for development\n- ⚡ **Error Handling** - Rich error information with stack traces\n- 📊 **Metadata** - API version, custom fields, timestamps\n- ✅ **Status Helpers** - IsSuccess(), IsClientError(), IsServerError()\n- 🔄 **JSON Parsing** - Parse JSON strings back to wrapper objects\n\n## Requirements\n\n- Go version 1.23 or higher\n\n## Installation\n\n### Install Package\n\n\u003e Latest version\n```bash\ngo get github.com/sivaosorg/replify@latest\n```\n\n\u003e Specific version\n```bash\ngo get github.com/sivaosorg/replify@v0.1.0\n```\n\n### Import in Code\n\n```go\nimport \"github.com/sivaosorg/replify\"\n```\n\nWith [Go's module support](https://go.dev/wiki/Modules#how-to-use-modules), `go [build|run|test]` automatically fetches the necessary dependencies when you add the import.\n\n## Quick Start\n\n### Basic Example\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/sivaosorg/replify\"\n)\n\nfunc main() {\n    // Create a simple success response\n    response := replify.New().\n        WithStatusCode(200).\n        WithMessage(\"User retrieved successfully\").\n        WithBody(map[string]string{\n            \"id\":   \"123\",\n            \"name\": \"John Doe\",\n        })\n    \n    fmt.Println(response.JSONPretty())\n}\n```\n\n**Output:**\n```json\n{\n    \"data\": {\n        \"id\": \"123\",\n        \"name\": \"John Doe\"\n    },\n    \"headers\": {\n        \"code\": 200,\n        \"text\": \"OK\"\n    },\n    \"message\": \"User retrieved successfully\",\n    \"meta\": {\n        \"api_version\": \"v0.0.1\",\n        \"locale\": \"en_US\",\n        \"request_id\": \"d7e5ce24b796da94770911db36565bf9\",\n        \"requested_time\": \"2026-01-29T10:07:05.751501+07:00\"\n    },\n    \"status_code\": 200,\n    \"total\": 0\n}\n```\n\n## Standard Response Format\n\nThe library produces responses in this standardized format:\n\n```json\n{\n  \"status_code\": 200,\n  \"message\": \"Resource retrieved successfully\",\n  \"path\": \"/api/v1/users\",\n  \"data\": [ // abstract data (can be array or object)\n    {\n      \"id\": \"user_01J6G7W9K2M4X7V5P8B3Q2Z1NS\",\n      \"username\": \"jdoe_dev\",\n      \"email\": \"j.doe@example.com\",\n      \"role\": \"administrator\",\n      \"status\": \"active\",\n      \"created_at\": \"2025-01-15T08:30:00Z\",\n      \"last_login\": \"2026-02-26T14:15:22Z\"\n    },\n    {\n      \"id\": \"user_01J6G7W9K2M4X7V5P8B3Q2Z1NT\",\n      \"username\": \"s_smith\",\n      \"email\": \"sarah.smith@example.com\",\n      \"role\": \"editor\",\n      \"status\": \"active\",\n      \"created_at\": \"2025-02-01T10:15:00Z\",\n      \"last_login\": \"2026-02-25T09:45:10Z\"\n    }\n  ],\n  \"pagination\": {\n    \"page\": 1,\n    \"per_page\": 2,\n    \"total_items\": 120,\n    \"total_pages\": 60,\n    \"is_last\": false\n  },\n  \"meta\": {\n    \"request_id\": \"req_80eafc6a1655ec5a06595d155f1e6951\",\n    \"api_version\": \"v1.0.4\",\n    \"locale\": \"en_US\",\n    \"requested_time\": \"2026-02-26T17:30:28.983Z\",\n    \"custom_fields\": { // custom fields\n      \"trace_id\": \"80eafc6a1655ec5a06595d155f1e6951\",\n      \"origin_region\": \"us-east-1\"\n    }\n  },\n  \"debug\": { // custom fields\n    \"trace_session_id\": \"4919e84fc26881e9fe790f5d07465db4\",\n    \"execution_time_ms\": 42\n  }\n}\n```\n\n### Field Descriptions\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `data` | `interface{}` | The primary data payload of the response |\n| `status_code` | `int` | HTTP status code for the response |\n| `message` | `string` | Human-readable message providing context |\n| `total` | `int` | Total number of items (used in non-paginated responses) |\n| `path` | `string` | Request path for which the response is generated |\n| `meta` | `object` | Metadata about the API response |\n| `meta.request_id` | `string` | Unique identifier for the request, useful for debugging |\n| `meta.api_version` | `string` | API version used for the request |\n| `meta.locale` | `string` | Locale used for the request (e.g., \"en_US\") |\n| `meta.requested_time` | `string` | Timestamp when the request was made (ISO 8601) |\n| `meta.custom_fields` | `object` | Additional custom metadata fields |\n| `pagination` | `object` | Pagination details, if applicable |\n| `pagination.page` | `int` | Current page number |\n| `pagination.per_page` | `int` | Number of items per page |\n| `pagination.total_items` | `int` | Total number of items available |\n| `pagination.total_pages` | `int` | Total number of pages |\n| `pagination.is_last` | `bool` | Indicates whether this is the last page |\n| `debug` | `object` | Debugging information (useful for development) |\n\n## Usage\n\n### 1. Creating Basic Responses\n\n#### Success Response\n\n```go\nresponse := replify.New().\n    WithStatusCode(200).\n    WithMessage(\"Operation successful\").\n    WithBody(data)\n```\n\n#### Error Response\n\n```go\nresponse := replify.New().\n    WithStatusCode(400).\n    WithError(\"Invalid input: email is required\").\n    WithMessage(\"Validation failed\")\n```\n\n#### Response with Metadata\n\n```go\nresponse := replify.New().\n    WithStatusCode(200).\n    WithBody(users).\n    WithRequestID(\"req-123-456\").\n    WithApiVersion(\"v1.0.0\").\n    WithLocale(\"en_US\").\n    WithPath(\"/api/v1/users\")\n```\n\n### 2. Pagination\n\n#### Creating Pagination\n\n```go\npagination := replify.Pages().\n    WithPage(1).\n    WithPerPage(20).\n    WithTotalItems(150).\n    WithTotalPages(8).\n    WithIsLast(false)\n\nresponse := replify.New().\n    WithStatusCode(200).\n    WithBody(users).\n    WithPagination(pagination).\n    WithTotal(20)\n```\n\n### 3. Debugging Information\n\n```go\nresponse := replify.New().\n    WithStatusCode(500).\n    WithError(\"Database connection failed\").\n    WithDebuggingKV(\"query\", \"SELECT * FROM users\").\n    WithDebuggingKV(\"error_code\", \"CONN_TIMEOUT\").\n    WithDebuggingKV(\"retry_count\", 3)\n```\n\n### 4. Complete Example\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/sivaosorg/replify\"\n    \"github.com/sivaosorg/replify/pkg/randn\"\n)\n\nfunc main() {\n    // Create pagination\n    p := replify.Pages().\n        WithIsLast(true).\n        WithPage(1000).\n        WithTotalItems(120).\n        WithTotalPages(34).\n        WithPerPage(2)\n    \n    // Create response\n    w := replify.New().\n        WithStatusCode(200).\n        WithTotal(1).\n        WithMessagef(\"How are you? %v\", \"I'm good\").\n        WithDebuggingKV(\"refer\", 1234).\n        WithDebuggingKVf(\"___abc\", \"trace sessions_id: %v\", randn.CryptoID()).\n        WithBody(\"response body here\").\n        WithPath(\"/api/v1/users\").\n        WithCustomFieldKVf(\"fields\", \"userID: %v\", 103).\n        WithPagination(p)\n    \n    if !w.Available() {\n        return\n    }\n    \n    // Access response properties\n    fmt.Println(w.JSON())\n    fmt.Println(w.StatusCode())\n    fmt.Println(w.StatusText())\n    fmt.Println(w.Message())\n    fmt.Println(w.Body())\n    fmt.Println(w.IsSuccess())\n    fmt.Println(w.Respond())\n    \n    // Check metadata\n    fmt.Println(w.Meta().IsCustomPresent())\n    fmt.Println(w.Meta().IsApiVersionPresent())\n    fmt.Println(w.Meta().IsRequestIDPresent())\n    fmt.Println(w.Meta().IsRequestedTimePresent())\n}\n```\n\n### 5. Parsing JSON to Response\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"log\"\n    \"time\"\n    \"github.com/sivaosorg/replify\"\n)\n\nfunc main() {\n    jsonStr := `{\n        \"data\": \"response body here\",\n        \"debug\": {\n          \"___abc\": \"trace sessions_id: 4919e84fc26881e9fe790f5d07465db4\",\n          \"refer\": 1234\n        },\n        \"message\": \"How do you do? I'm good\",\n        \"meta\": {\n          \"api_version\": \"v0.0.1\",\n          \"custom_fields\": {\n            \"fields\": \"userID: 103\"\n          },\n          \"locale\": \"en_US\",\n          \"request_id\": \"80eafc6a1655ec5a06595d155f1e6951\",\n          \"requested_time\": \"2024-12-14T20:24:23.983839+07:00\"\n        },\n        \"pagination\": {\n          \"is_last\": true,\n          \"page\": 1000,\n          \"per_page\": 2,\n          \"total_items\": 120,\n          \"total_pages\": 34\n        },\n        \"path\": \"/api/v1/users\",\n        \"status_code\": 200,\n        \"total\": 1\n    }`\n    \n    t := time.Now()\n    w, err := replify.UnwrapJSON(jsonStr)\n    diff := time.Since(t)\n    \n    if err != nil {\n        log.Fatalf(\"Error parsing JSON: %v\", err)\n    }\n    \n    fmt.Printf(\"Exe time: %+v\\n\", diff.String())\n    fmt.Printf(\"%+v\\n\", w.OnDebugging(\"___abc\"))\n    fmt.Printf(\"%+v\\n\", w.JSONPretty())\n}\n```\n\n## Practical Examples\n\n### Example 1: RESTful CRUD API\n\n```go\npackage main\n\nimport (\n    \"encoding/json\"\n    \"net/http\"\n    \"github.com/sivaosorg/replify\"\n)\n\ntype User struct {\n    ID    int    `json:\"id\"`\n    Name  string `json:\"name\"`\n    Email string `json:\"email\"`\n}\n\n// GET /users/:id\nfunc GetUser(w http.ResponseWriter, r *http.Request) {\n    id := getIDFromPath(r)\n    user, err := findUserByID(id)\n    \n    var response *replify.R\n    if err != nil {\n        response = replify.New().\n            WithStatusCode(404).\n            WithError(err.Error()).\n            WithMessage(\"User not found\").\n            WithRequestID(r.Header.Get(\"X-Request-ID\"))\n    } else {\n        response = replify.New().\n            WithStatusCode(200).\n            WithBody(user).\n            WithMessage(\"User retrieved successfully\").\n            WithRequestID(r.Header.Get(\"X-Request-ID\"))\n    }\n    \n    respondJSON(w, response)\n}\n\n// POST /users\nfunc CreateUser(w http.ResponseWriter, r *http.Request) {\n    var user User\n    if err := json.NewDecoder(r.Body).Decode(\u0026user); err != nil {\n        response := replify.New().\n            WithStatusCode(400).\n            WithError(err.Error()).\n            WithMessage(\"Invalid request body\")\n        respondJSON(w, response)\n        return\n    }\n    \n    if err := validateUser(user); err != nil {\n        response := replify.New().\n            WithStatusCode(422).\n            WithError(err.Error()).\n            WithMessage(\"Validation failed\")\n        respondJSON(w, response)\n        return\n    }\n    \n    createdUser, err := createUser(user)\n    if err != nil {\n        response := replify.New().\n            WithStatusCode(500).\n            WithErrorAck(err).\n            WithMessage(\"Failed to create user\")\n        respondJSON(w, response)\n        return\n    }\n    \n    response := replify.New().\n        WithStatusCode(201).\n        WithBody(createdUser).\n        WithMessage(\"User created successfully\")\n    respondJSON(w, response)\n}\n\nfunc respondJSON(w http.ResponseWriter, response *replify.R) {\n    w.Header().Set(\"Content-Type\", \"application/json\")\n    w.WriteHeader(response.StatusCode())\n    w.Write([]byte(response.JSON()))\n}\n```\n\n### Example 2: Paginated List API\n\n```go\nfunc ListUsers(w http.ResponseWriter, r *http.Request) {\n    // Parse query parameters\n    page := getQueryInt(r, \"page\", 1)\n    perPage := getQueryInt(r, \"per_page\", 10)\n    search := r.URL.Query().Get(\"search\")\n    \n    // Fetch users with pagination\n    users, total, err := db.FindUsers(search, page, perPage)\n    if err != nil {\n        response := replify.New().\n            WithStatusCode(500).\n            WithErrorAck(err).\n            WithMessage(\"Failed to fetch users\").\n            WithDebuggingKV(\"search\", search).\n            WithDebuggingKV(\"page\", page)\n        respondJSON(w, response)\n        return\n    }\n    \n    // Calculate pagination metadata\n    totalPages := (total + perPage - 1) / perPage\n    isLast := page \u003e= totalPages\n    \n    pagination := replify.Pages().\n        WithPage(page).\n        WithPerPage(perPage).\n        WithTotalItems(total).\n        WithTotalPages(totalPages).\n        WithIsLast(isLast)\n    \n    response := replify.New().\n        WithStatusCode(200).\n        WithBody(users).\n        WithPagination(pagination).\n        WithTotal(len(users)).\n        WithMessage(\"Users retrieved successfully\").\n        WithPath(r.URL.Path).\n        WithRequestID(r.Header.Get(\"X-Request-ID\"))\n    \n    respondJSON(w, response)\n}\n```\n\n### Example 3: Error Handling with Stack Traces\n\n```go\nfunc ProcessOrder(w http.ResponseWriter, r *http.Request) {\n    order, err := processOrderLogic(r)\n    \n    response := replify.New()\n    \n    if err != nil {\n        response.\n            WithStatusCode(500).\n            WithErrorAck(err).\n            WithMessage(\"Order processing failed\")\n        \n        // Add debug info in development\n        if os.Getenv(\"ENV\") == \"development\" {\n            response.\n                WithDebuggingKV(\"timestamp\", time.Now()).\n                WithDebuggingKV(\"stack_trace\", err.Error()).\n                WithDebuggingKV(\"order_data\", order)\n        }\n    } else {\n        response.\n            WithStatusCode(200).\n            WithBody(order).\n            WithMessage(\"Order processed successfully\")\n    }\n    \n    respondJSON(w, response)\n}\n```\n\n## API Reference\n\n### Wrapper Type (R)\n\n```go\ntype R struct {\n    *wrapper\n}\n```\n\nThe `R` type is a high-level abstraction providing a simplified interface for handling API responses.\n\n### Core Functions\n\n| Function | Description |\n|----------|-------------|\n| `New() *wrapper` | Creates a new response wrapper |\n| `Pages() *pagination` | Creates a new pagination object |\n| `UnwrapJSON(jsonStr string) (*wrapper, error)` | Parses JSON string to wrapper |\n\n### Configuration Methods\n\n#### Response Configuration\n\n| Method | Description |\n|--------|-------------|\n| `WithStatusCode(code int)` | Sets HTTP status code |\n| `WithBody(v interface{})` | Sets response body/data |\n| `WithMessage(message string)` | Sets response message |\n| `WithMessagef(format string, args...)` | Sets formatted message |\n| `WithError(message string)` | Sets error message |\n| `WithErrorf(format string, args...)` | Sets formatted error |\n| `WithErrorAck(err error)` | Sets error with stack trace |\n| `AppendError(err error, message string)` | Wraps error with context |\n| `AppendErrorf(err error, format string, args...)` | Wraps error with formatted context |\n| `WithPath(v string)` | Sets request path |\n| `WithPathf(v string, args...)` | Sets formatted request path |\n| `WithTotal(total int)` | Sets total items count |\n\n#### Metadata Methods\n\n| Method | Description |\n|--------|-------------|\n| `WithRequestID(v string)` | Sets request ID |\n| `WithRequestIDf(format string, args...)` | Sets formatted request ID |\n| `WithApiVersion(v string)` | Sets API version |\n| `WithApiVersionf(format string, args...)` | Sets formatted API version |\n| `WithLocale(v string)` | Sets locale (e.g., \"en_US\") |\n| `WithRequestedTime(v time.Time)` | Sets request timestamp |\n| `WithCustomFieldKV(key string, value interface{})` | Adds custom metadata field |\n| `WithCustomFieldKVf(key, format string, args...)` | Adds formatted custom field |\n| `WithCustomFields(values map[string]interface{})` | Sets multiple custom fields |\n| `WithMeta(v *meta)` | Sets entire metadata object |\n| `WithHeader(v *header)` | Sets the header |\n\n#### Pagination Methods\n\n| Method | Description |\n|--------|-------------|\n| `WithPagination(v *pagination)` | Sets pagination object |\n| `WithPage(v int)` | Sets current page number |\n| `WithPerPage(v int)` | Sets items per page |\n| `WithTotalItems(v int)` | Sets total items count |\n| `WithTotalPages(v int)` | Sets total pages count |\n| `WithIsLast(v bool)` | Sets if current page is last |\n\n#### Debugging Methods\n\n| Method | Description |\n|--------|-------------|\n| `WithDebugging(v map[string]interface{})` | Sets debug information map |\n| `WithDebuggingKV(key string, value interface{})` | Adds single debug key-value |\n| `WithDebuggingKVf(key, format string, args...)` | Adds formatted debug value |\n\n### Query Methods\n\n| Method | Returns | Description |\n|--------|---------|-------------|\n| `Available()` | `bool` | Checks if wrapper is non-nil |\n| `StatusCode()` | `int` | Gets HTTP status code |\n| `StatusText()` | `string` | Gets status text (e.g., \"OK\") |\n| `Body()` | `interface{}` | Gets response body |\n| `Message()` | `string` | Gets response message |\n| `Error()` | `string` | Gets error message |\n| `Cause()` | `error` | Gets underlying error cause |\n| `Total()` | `int` | Gets total items |\n| `Meta()` | `*meta` | Gets metadata object |\n| `Header()` | `*header` | Gets header object |\n| `Pagination()` | `*pagination` | Gets pagination object |\n| `Debugging()` | `map[string]interface{}` | Gets debug information |\n| `OnDebugging(key string)` | `interface{}` | Gets specific debug value |\n\n### Conditional Check Methods\n\n| Method | Returns | Description |\n|--------|---------|-------------|\n| `IsSuccess()` | `bool` | Checks if status is 2xx |\n| `IsClientError()` | `bool` | Checks if status is 4xx |\n| `IsServerError()` | `bool` | Checks if status is 5xx |\n| `IsRedirection()` | `bool` | Checks if status is 3xx |\n| `IsError()` | `bool` | Checks if error exists or status is 4xx/5xx |\n| `IsErrorPresent()` | `bool` | Checks if error field exists |\n| `IsBodyPresent()` | `bool` | Checks if body exists |\n| `IsPagingPresent()` | `bool` | Checks if pagination exists |\n| `IsMetaPresent()` | `bool` | Checks if metadata exists |\n| `IsHeaderPresent()` | `bool` | Checks if header exists |\n| `IsDebuggingPresent()` | `bool` | Checks if debug info exists |\n| `IsDebuggingKeyPresent(key string)` | `bool` | Checks if specific debug key exists |\n| `IsLastPage()` | `bool` | Checks if current page is last |\n| `IsStatusCodePresent()` | `bool` | Checks if valid status code exists |\n| `IsTotalPresent()` | `bool` | Checks if total count exists |\n\n### Serialization Methods\n\n| Method | Returns | Description |\n|--------|---------|-------------|\n| `JSON()` | `string` | Returns compact JSON string |\n| `JSONPretty()` | `string` | Returns pretty-printed JSON |\n| `Respond()` | `map[string]interface{}` | Returns map representation |\n| `Reply()` | `R` | Returns R wrapper |\n\n## HTTP Status Codes Reference\n\n### Common API Scenarios\n\n| **Scenario** | **HTTP Status Codes** | **Example** |\n|--------------|----------------------|-------------|\n| **Successful Resource Retrieval** | 200 OK, 304 Not Modified | `GET /users/123` - Returns user data |\n| **Resource Creation** | 201 Created | `POST /users` - Creates a new user |\n| **Asynchronous Processing** | 202 Accepted | `POST /large-file` - File upload starts |\n| **Validation Errors** | 400 Bad Request | `POST /users` - Missing required field |\n| **Authentication Issues** | 401 Unauthorized, 403 Forbidden | Invalid credentials or permissions |\n| **Rate Limiting** | 429 Too Many Requests | Exceeded API request limits |\n| **Missing Resource** | 404 Not Found | `GET /users/999` - User not found |\n| **Server Failures** | 500 Internal Server Error, 503 Service Unavailable | Database failure or maintenance |\n| **Version Conflicts** | 409 Conflict | Outdated version causing conflict |\n\n### Detailed Status Codes\n\n#### Success (2xx)\n\n| Code | Status | Use Case |\n|------|--------|----------|\n| 200 | OK | Successful GET, PUT, PATCH |\n| 201 | Created | Successful POST (resource created) |\n| 202 | Accepted | Async processing started |\n| 204 | No Content | Successful DELETE |\n| 206 | Partial Content | Video streaming, range requests |\n\n#### Redirection (3xx)\n\n| Code | Status | Use Case |\n|------|--------|----------|\n| 301 | Moved Permanently | Resource permanently moved |\n| 302 | Found | Temporary redirect |\n| 304 | Not Modified | Cached content still valid |\n| 307 | Temporary Redirect | POST redirect maintaining method |\n| 308 | Permanent Redirect | Permanent redirect maintaining method |\n\n#### Client Errors (4xx)\n\n| Code | Status | Use Case |\n|------|--------|----------|\n| 400 | Bad Request | Invalid request format/data |\n| 401 | Unauthorized | Missing/invalid authentication |\n| 403 | Forbidden | Insufficient permissions |\n| 404 | Not Found | Resource doesn't exist |\n| 409 | Conflict | Resource conflict (duplicate) |\n| 413 | Payload Too Large | Request body too large |\n| 415 | Unsupported Media Type | Invalid content type |\n| 422 | Unprocessable Entity | Validation errors |\n| 429 | Too Many Requests | Rate limiting |\n\n#### Server Errors (5xx)\n\n| Code | Status | Use Case |\n|------|--------|----------|\n| 500 | Internal Server Error | Unexpected server error |\n| 501 | Not Implemented | Feature not implemented |\n| 502 | Bad Gateway | Upstream service error |\n| 503 | Service Unavailable | Service down/maintenance |\n| 504 | Gateway Timeout | Upstream timeout |\n\n## Best Practices\n\n### ✅ Do's\n\n1. **Always set status codes**\n   ```go\n   response := replify.New().\n       WithStatusCode(200).\n       WithBody(data)\n   ```\n\n2. **Use request IDs for tracing**\n   ```go\n   response := replify.New().\n       WithRequestID(r.Header.Get(\"X-Request-ID\")).\n       WithBody(data)\n   ```\n\n3. **Include API version**\n   ```go\n   response := replify.New().\n       WithApiVersion(\"v1.0.0\").\n       WithBody(data)\n   ```\n\n4. **Use WithErrorAck for stack traces**\n   ```go\n   response := replify.New().\n       WithStatusCode(500).\n       WithErrorAck(err)\n   ```\n\n5. **Check response status before processing**\n   ```go\n   if response.IsSuccess() {\n       processData(response.Body())\n   }\n   ```\n\n6. **Use pagination for list endpoints**\n   ```go\n   pagination := replify.Pages().\n       WithPage(page).\n       WithPerPage(perPage).\n       WithTotalItems(total)\n   ```\n\n### ❌ Don'ts\n\n1. **Don't forget to set status codes**\n   ```go\n   // ❌ Bad\n   response := replify.New().WithBody(data)\n   \n   // ✅ Good\n   response := replify.New().WithStatusCode(200).WithBody(data)\n   ```\n\n2. **Don't expose sensitive debug info in production**\n   ```go\n   // ❌ Bad\n   response := replify.New().\n       WithDebuggingKV(\"database_password\", dbPass)\n   \n   // ✅ Good\n   if os.Getenv(\"ENV\") == \"development\" {\n       response.WithDebuggingKV(\"query\", sqlQuery)\n   }\n   ```\n\n3. **Don't use generic error messages**\n   ```go\n   // ❌ Bad\n   WithError(\"Error occurred\")\n   \n   // ✅ Good\n   WithError(\"Failed to create user: email already exists\")\n   ```\n\n4. **Don't ignore error checking**\n   ```go\n   // ❌ Bad\n   wrapper, _ := replify.UnwrapJSON(jsonStr)\n   \n   // ✅ Good\n   wrapper, err := replify.UnwrapJSON(jsonStr)\n   if err != nil {\n       log.Printf(\"Failed to parse JSON: %v\", err)\n   }\n   ```\n\n## Use Cases\n\n### ✅ When to Use\n\n- **RESTful API Development** - Standardizing API responses\n- **Microservices** - Consistent responses across services\n- **API Versioning** - Including version metadata\n- **Error Standardization** - Consistent error formats\n- **Pagination** - APIs returning paginated results\n- **Multi-tenant APIs** - Including tenant/locale information\n- **Request Tracing** - Tracking requests across services\n- **Development Debugging** - Conditional debug information\n\n### ❌ When Not to Use\n\n- **GraphQL APIs** - GraphQL has its own response format\n- **gRPC Services** - Protocol Buffers define the structure\n- **WebSocket APIs** - Real-time bidirectional communication\n- **Simple CLIs** - Overkill for command-line tools\n- **Internal Services** - Where custom formats are required\n- **High-Performance** - Direct JSON encoding may be faster\n\n## fj Usage Guide\n\n`fj` (_Fast JSON_) is the JSON path-extraction engine embedded in **replify**. It lets you read, query, and transform values from a JSON document **without unmarshalling the entire structure** into Go types. It lives in `pkg/fj` and is exposed through the `wrapper` type in `parser.go`.\n\n### Purpose in the replify Architecture\n\nWhen a `wrapper` carries a JSON body, `fj` powers every field-level query on that body. Instead of decoding the whole payload into a `map[string]any` or a concrete struct, `fj` walks the raw string just far enough to locate the requested path. This keeps allocations low and throughput high on hot request paths.\n\n```\nHTTP Request → wrapper.WithBody(data) → wrapper.QueryJSONBody(\"user.name\")\n                                                 ↓\n                                    fj.Get(jsonString, \"user.name\")\n                                                 ↓\n                                          fj.Context  ← single value, no full decode\n```\n\n### When to Use fj Instead of encoding/json\n\n| Scenario | Recommended approach |\n|---|---|\n| Extract one or a few fields from a large response body | `fj` / `QueryJSONBody` |\n| Validate that the body is well-formed JSON | `fj.IsValidJSON` / `ValidJSONBody` |\n| Search leaf values or keys across an unknown schema | `fj.Search` / `SearchJSONBody*` |\n| Apply streaming transforms (pretty-print, minify, etc.) | `fj` transformers |\n| Bind the full payload into a typed struct | `encoding/json` or `json-iterator` |\n| Write or modify JSON | `encoding/json` |\n| JSON schema validation | a dedicated schema library |\n\n### Path Syntax Quick Reference\n\n```\nuser.name              field access\nroles.0                array index\nroles.#                array length\nroles.#.name           collect field from every element\nroles.#(role==\"admin\") first element where role == \"admin\"\nroles.#(role==\"admin\")# all elements where role == \"admin\"\n{id,name}              multi-selector → new object\n[id,name]              multi-selector → new array\nname.@uppercase        built-in transformer\nname.@word:upper       transformer with argument\n..title                recursive descent (JSON Lines / deep scan)\n```\n\nDots and wildcards in key names can be escaped with a backslash (`\\`).\n\n### Core API\n\n#### Direct fj usage\n\n```go\nimport \"github.com/sivaosorg/replify/pkg/fj\"\n\njson := `{\n    \"user\": {\"name\": \"Alice\", \"age\": 30, \"active\": true},\n    \"roles\": [\"admin\", \"editor\"],\n    \"scores\": [95, 87, 92]\n}`\n\n// Single path\nname := fj.Get(json, \"user.name\").String()    // \"Alice\"\nage  := fj.Get(json, \"user.age\").Int64()      // 30\nok   := fj.Get(json, \"user.active\").Bool()    // true\nn    := fj.Get(json, \"roles.#\").Int()         // 2 (array length)\n\n// Multiple paths in one pass\nresults := fj.GetMulti(json, \"user.name\", \"user.age\", \"roles.#\")\n// results[0].String() == \"Alice\", results[1].Int64() == 30, results[2].Int() == 2\n\n// Check presence before use\nif ctx := fj.Get(json, \"user.email\"); ctx.Exists() {\n    fmt.Println(ctx.String())\n}\n\n// Parse a document once, query multiple times (avoids re-parsing)\ndoc := fj.Parse(json)\nfmt.Println(doc.Get(\"user.name\").String())\nfmt.Println(doc.Get(\"roles.0\").String())\n```\n\n#### Zero-copy byte-slice access\n\n`GetBytes` is preferred when you already hold a `[]byte`. It uses `unsafe` pointer operations internally to avoid an extra string allocation:\n\n```go\nrawBytes := []byte(`{\"id\":42,\"status\":\"active\"}`)\n\nid     := fj.GetBytes(rawBytes, \"id\").Int()        // 42\nstatus := fj.GetBytes(rawBytes, \"status\").String() // \"active\"\n\n// Multiple paths from bytes\nres := fj.GetBytesMulti(rawBytes, \"id\", \"status\")\n```\n\n\u003e **Memory note**: `fj.Context.Raw()` returns a substring view of the original string without copying. Do not hold a reference to the `Context` after the source string has been released; the backing memory will be reclaimed.\n\n### Wrapper Integration (parser.go)\n\nThe `wrapper` type exposes all `fj` operations without requiring you to import `pkg/fj` directly in most cases:\n\n```go\n\tresponse := replify.New().\n\t\tWithStatusCode(200).\n\t\tWithBody(map[string]any{\n\t\t\t\"user\": map[string]any{\"name\": \"Alice\", \"role\": \"admin\"},\n\t\t\t\"items\": []map[string]any{\n\t\t\t\t{\"id\": 1, \"price\": 9.99},\n\t\t\t\t{\"id\": 2, \"price\": 4.50},\n\t\t\t},\n\t\t})\n\n\t// Single path query\n\tname := response.QueryJSONBody(\"user.name\").String() // \"Alice\"\n\n\t// Multiple paths in one call (one JSON serialization)\n\tfields := response.QueryJSONBodyMulti(\"user.name\", \"user.role\")\n\tfmt.Println(fields[0].String(), fields[1].String()) // Alice admin\n\n\t// Parse the body once and chain subsequent queries\n\tctx := response.JSONBodyParser()\n\tfmt.Println(ctx.Get(\"user.name\").String())\n\tfmt.Println(ctx.Get(\"items.#\").Int()) // array length\n\n\t// Validate the body\n\tif !response.ValidJSONBody() {\n\t\tlog.Println(\"body is not valid JSON\")\n\t\treturn\n\t}\n\n\t// Aggregate helpers\n\ttotal := response.SumJSONBody(\"items.#.price\")  // 14.49\n\tmin, _ := response.MinJSONBody(\"items.#.price\") // 4.50\n\tmax, _ := response.MaxJSONBody(\"items.#.price\") // 9.99\n\tavg, _ := response.AvgJSONBody(\"items.#.price\") // 7.245\n\n\tfmt.Println(name)\n\tfmt.Println(fields[0].String(), fields[1].String())\n\tfmt.Println(ctx.Get(\"user.name\").String())\n\tfmt.Println(ctx.Get(\"items.#\").Int())\n\tfmt.Println(total)\n\tfmt.Println(min)\n\tfmt.Println(max)\n\tfmt.Println(avg)\n```\n\n\u003e **Performance tip**: `QueryJSONBody` serializes the body on every call. For repeated queries on the same body, call `JSONBodyParser()` once and reuse the returned `fj.Context`.\n\n### Context Value Extraction\n\nA `fj.Context` is returned by every query. Always call `.Exists()` before using the value if the path might be absent.\n\n```go\nctx := fj.Get(json, \"optional.field\")\n\nctx.Exists()   // false when path is missing\nctx.Kind()     // fj.Null | fj.String | fj.Number | fj.True | fj.False | fj.JSON\nctx.String()   // string representation\nctx.Bool()     // bool\nctx.Int()      // int\nctx.Int64()    // int64\nctx.Float64()  // float64\nctx.Raw()      // raw JSON token (no allocation)\nctx.IsArray()  // true when kind == JSON and raw starts with '['\nctx.IsObject() // true when kind == JSON and raw starts with '{'\nctx.IsError()  // true if parsing produced an error\nctx.Cause()    // error string, or \"\" if no error\n\n// Iterate array values\nctx.Foreach(func(key, val fj.Context) bool {\n    fmt.Println(val.String())\n    return true // return false to stop\n})\n```\n\n### Transformers\n\nTransformers are applied with the `@` prefix inside a path expression and receive the current JSON value as input. An optional argument is passed after a `:` separator.\n\n```\npath.@transformerName\npath.@transformerName:argument\npath.@transformerName:{\"key\":\"value\"}\n```\n\n#### Core transformers\n\n| Transformer | Alias(es) | Input | Description |\n|---|---|---|---|\n| `@pretty` | — | any | Pretty-print (indented) JSON. Accepts optional `{\"sort_keys\":true,\"indent\":\"\\t\",\"prefix\":\"\",\"width\":80}`. |\n| `@minify` | `@ugly` | any | Compact single-line JSON (all whitespace removed). |\n| `@valid` | — | any | Returns `\"true\"` / `\"false\"` — whether the input is valid JSON. |\n| `@this` | — | any | Identity — returns the input unchanged. |\n| `@reverse` | — | array \\| object | Reverses element order (array) or key order (object). |\n| `@flatten` | — | array | Shallow-flatten nested arrays. Pass `{\"deep\":true}` to recurse. |\n| `@join` | — | array of objects | Merge an array of objects into one object. Pass `{\"preserve\":true}` to keep duplicate keys. |\n| `@keys` | — | object | Return a JSON array of the object's keys. |\n| `@values` | — | object | Return a JSON array of the object's values. |\n| `@group` | — | object of arrays | Zip object-of-arrays into an array-of-objects. |\n| `@search` | — | any | `@search:path` — collect all values reachable at `path` anywhere in the tree. |\n| `@json` | — | string | Parse the string as JSON and return the value. |\n| `@string` | — | any | Encode the value as a JSON string literal. |\n\n#### String transformers\n\n| Transformer | Alias(es) | Description |\n|---|---|---|\n| `@uppercase` | `@upper` | Convert all characters to upper-case. |\n| `@lowercase` | `@lower` | Convert all characters to lower-case. |\n| `@flip` | — | Reverse the characters of the string. |\n| `@trim` | — | Strip leading/trailing whitespace. |\n| `@snakecase` | `@snake`, `@snakeCase` | Convert to `snake_case`. |\n| `@camelcase` | `@camel`, `@camelCase` | Convert to `camelCase`. |\n| `@kebabcase` | `@kebab`, `@kebabCase` | Convert to `kebab-case`. |\n| `@replace` | — | `@replace:{\"target\":\"old\",\"replacement\":\"new\"}` — replace first occurrence. |\n| `@replaceAll` | — | `@replaceAll:{\"target\":\"old\",\"replacement\":\"new\"}` — replace all occurrences. |\n| `@hex` | — | Hex-encode the value. |\n| `@bin` | — | Binary-encode the value. |\n| `@insertAt` | — | `@insertAt:{\"index\":5,\"insert\":\"XYZ\"}` — insert a substring at position. |\n| `@wc` | — | Return the word-count of a string as an integer. |\n| `@padLeft` | — | `@padLeft:{\"padding\":\"*\",\"length\":10}` — left-pad to a fixed width. |\n| `@padRight` | — | `@padRight:{\"padding\":\"*\",\"length\":10}` — right-pad to a fixed width. |\n\n#### Object transformers\n\n| Transformer | Description |\n|---|---|\n| `@project` | Pick and/or rename fields from an object. Arg: `{\"pick\":[\"f1\",\"f2\"],\"rename\":{\"f1\":\"newName\"}}`. Omit `pick` to keep all fields; omit `rename` for no renaming. |\n| `@default` | Inject fallback values for fields that are absent or `null`. Arg: `{\"field\":\"defaultValue\",...}`. Existing non-null fields are never overwritten. |\n\n#### Array transformers\n\n| Transformer | Description |\n|---|---|\n| `@filter` | Keep only elements matching a condition. Arg: `{\"key\":\"field\",\"op\":\"eq\",\"value\":val}`. Operators: `eq` (default), `ne`, `gt`, `gte`, `lt`, `lte`, `contains`. |\n| `@pluck` | Extract a named field (supports dot-notation paths) from every element. Arg: field path string, e.g. `@pluck:name` or `@pluck:addr.city`. |\n| `@first` | Return the first element of the array, or `null` if empty. |\n| `@last` | Return the last element of the array, or `null` if empty. |\n| `@count` | Return the number of elements (array) or key-value pairs (object) as an integer. Scalars return `0`. |\n| `@sum` | Sum all numeric values in the array; non-numeric elements are skipped. Returns `0` for empty arrays. |\n| `@min` | Return the minimum numeric value in the array. Returns `null` when no numbers are present. |\n| `@max` | Return the maximum numeric value in the array. Returns `null` when no numbers are present. |\n\n#### Value normalization transformers\n\n| Transformer | Description |\n|---|---|\n| `@coerce` | Convert a scalar to a target type. Arg: `{\"to\":\"string\"}`, `{\"to\":\"number\"}`, or `{\"to\":\"bool\"}`. Objects and arrays are returned unchanged. |\n\n#### Examples\n\n```go\njson := `{\n    \"user\": {\"name\": \"Alice\", \"role\": null, \"age\": 30, \"city\": \"NY\"},\n    \"scores\": [95, 87, 92, 78],\n    \"users\": [\n        {\"name\": \"Alice\", \"active\": true,  \"addr\": {\"city\": \"NY\"}},\n        {\"name\": \"Bob\",   \"active\": false, \"addr\": {\"city\": \"LA\"}},\n        {\"name\": \"Carol\", \"active\": true,  \"addr\": {\"city\": \"NY\"}}\n    ]\n}`\n\n// ── Core ─────────────────────────────────────────────────────────────────────\nfj.Get(json, \"@pretty\").String()             // indented JSON\nfj.Get(json, \"@minify\").String()             // compact JSON\nfj.Get(json, \"user.@keys\").String()          // [\"name\",\"role\",\"age\",\"city\"]\nfj.Get(json, \"user.@values\").String()        // [\"Alice\",null,30,\"NY\"]\nfj.Get(json, \"user.@valid\").String()         // \"true\"\n\n// ── String ───────────────────────────────────────────────────────────────────\nfj.Get(json, \"user.name.@uppercase\").String()   // \"ALICE\"\nfj.Get(json, \"user.name.@reverse\").String()     // \"ecilA\"\nfj.Get(json, \"user.name.@snakecase\").String()   // \"alice\"\nfj.Get(json, \"user.city.@padLeft:{\\\"padding\\\":\\\"0\\\",\\\"length\\\":6}\").String() // \"000 NY\"\n\n// ── Object ───────────────────────────────────────────────────────────────────\n\n// Project: keep only name and age, rename age → years\nfj.Get(json, `user.@project:{\"pick\":[\"name\",\"age\"],\"rename\":{\"age\":\"years\"}}`).Raw()\n// → {\"name\":\"Alice\",\"years\":30}\n\n// Default: fill in missing / null fields\nfj.Get(json, `user.@default:{\"role\":\"viewer\",\"active\":true}`).Raw()\n// → {\"name\":\"Alice\",\"role\":\"viewer\",\"age\":30,\"city\":\"NY\",\"active\":true}\n\n// ── Array ────────────────────────────────────────────────────────────────────\n\n// Filter: keep only active users\nfj.Get(json, `users.@filter:{\"key\":\"active\",\"value\":true}`).Raw()\n// → [{\"name\":\"Alice\",\"active\":true,...},{\"name\":\"Carol\",\"active\":true,...}]\n\n// Pluck: extract the city from every user's address\nfj.Get(json, `users.@pluck:addr.city`).Raw()\n// → [\"NY\",\"LA\",\"NY\"]\n\n// Aggregation helpers\nfj.Get(json, \"scores.@first\").Raw()    // 95\nfj.Get(json, \"scores.@last\").Raw()     // 78\nfj.Get(json, \"scores.@count\").Raw()    // 4\nfj.Get(json, \"scores.@sum\").Raw()      // 352\nfj.Get(json, \"scores.@min\").Raw()      // 78\nfj.Get(json, \"scores.@max\").Raw()      // 95\n\n// ── Coerce ───────────────────────────────────────────────────────────────────\nfj.Get(`42`,   `@coerce:{\"to\":\"string\"}`).Raw()  // \"42\"\nfj.Get(`\"99\"`, `@coerce:{\"to\":\"number\"}`).Raw()  // 99\nfj.Get(`1`,    `@coerce:{\"to\":\"bool\"}`).Raw()    // true\n```\n\n#### Composing transformers\n\nTransformers can be chained using the `|` pipe operator or dot notation:\n\n```go\n// First filter the array, then count the remaining elements\nfj.Get(json, `users.@filter:{\"key\":\"active\",\"value\":true}|@count`).Raw()\n// → 2\n\n// Pluck names, then reverse the resulting array\nfj.Get(json, `users.@pluck:name|@reverse`).Raw()\n// → [\"Carol\",\"Bob\",\"Alice\"]\n```\n\n#### Complex real-world examples\n\nThe following scenarios demonstrate how to combine multiple transformers into a single expression to process realistic JSON payloads.\n\n---\n\n**Example 1 — E-commerce product catalog: filter, aggregate, and shape**\n\n```go\ncatalog := `{\n    \"products\": [\n        {\"id\":\"p1\",\"name\":\"Laptop Pro\",    \"category\":\"electronics\",\"price\":1299.99,\"stock\":5},\n        {\"id\":\"p2\",\"name\":\"USB-C Hub\",     \"category\":\"electronics\",\"price\":49.99,  \"stock\":120},\n        {\"id\":\"p3\",\"name\":\"Desk Chair\",    \"category\":\"furniture\",  \"price\":349.00, \"stock\":0},\n        {\"id\":\"p4\",\"name\":\"Standing Desk\", \"category\":\"furniture\",  \"price\":699.00, \"stock\":3},\n        {\"id\":\"p5\",\"name\":\"Webcam HD\",     \"category\":\"electronics\",\"price\":89.99,  \"stock\":45}\n    ]\n}`\n\n// All in-stock electronics names\nfj.Get(catalog, `products.@filter:{\"key\":\"category\",\"value\":\"electronics\"}|@filter:{\"key\":\"stock\",\"op\":\"gt\",\"value\":0}|@pluck:name`).Raw()\n// → [\"Laptop Pro\",\"USB-C Hub\",\"Webcam HD\"]\n\n// Count of in-stock products\nfj.Get(catalog, `products.@filter:{\"key\":\"stock\",\"op\":\"gt\",\"value\":0}|@count`).Raw()\n// → 4\n\n// Price range of in-stock products\nfj.Get(catalog, `products.@filter:{\"key\":\"stock\",\"op\":\"gt\",\"value\":0}|@pluck:price|@min`).Raw()\n// → 49.99\nfj.Get(catalog, `products.@filter:{\"key\":\"stock\",\"op\":\"gt\",\"value\":0}|@pluck:price|@max`).Raw()\n// → 1299.99\n\n// Project the first in-stock product as a display card (pick and rename fields)\nfirst := fj.Get(catalog, `products.@filter:{\"key\":\"stock\",\"op\":\"gt\",\"value\":0}|@first`).Raw()\nfj.Get(first, `@project:{\"pick\":[\"name\",\"price\"],\"rename\":{\"name\":\"title\",\"price\":\"cost\"}}`).Raw()\n// → {\"title\":\"Laptop Pro\",\"cost\":1299.99}\n```\n\n---\n\n**Example 2 — API response normalization: fill defaults then project and rename**\n\n```go\n// Raw user record from an external API with null / absent fields\nrawUser := `{\"id\":\"u1\",\"name\":\"Alice\",\"role\":null,\"verified\":null}`\n\n// One-shot normalization: fill nulls → keep only safe fields → rename id for the frontend\nfj.Get(rawUser, `@default:{\"role\":\"viewer\",\"verified\":false}|@project:{\"pick\":[\"id\",\"name\",\"role\",\"verified\"],\"rename\":{\"id\":\"userId\"}}`).Raw()\n// → {\"userId\":\"u1\",\"name\":\"Alice\",\"role\":\"viewer\",\"verified\":false}\n```\n\n---\n\n**Example 3 — Log processing: filter, count, and retrieve the latest entry**\n\n```go\nlogs := `[\n    {\"level\":\"error\",\"msg\":\"Connection refused\",\"ts\":1700001},\n    {\"level\":\"info\", \"msg\":\"Server started\",    \"ts\":1700002},\n    {\"level\":\"error\",\"msg\":\"Timeout exceeded\",  \"ts\":1700003},\n    {\"level\":\"warn\", \"msg\":\"High memory\",       \"ts\":1700004}\n]`\n\n// How many errors?\nfj.Get(logs, `@filter:{\"key\":\"level\",\"value\":\"error\"}|@count`).Raw()\n// → 2\n\n// All error messages\nfj.Get(logs, `@filter:{\"key\":\"level\",\"value\":\"error\"}|@pluck:msg`).Raw()\n// → [\"Connection refused\",\"Timeout exceeded\"]\n\n// Most recent error entry (last in the filtered array)\nfj.Get(logs, `@filter:{\"key\":\"level\",\"value\":\"error\"}|@last`).Raw()\n// → {\"level\":\"error\",\"msg\":\"Timeout exceeded\",\"ts\":1700003}\n```\n\n---\n\n**Example 4 — Nested data aggregation: filter → pluck → flatten → sum**\n\n```go\nteamData := `{\n    \"teams\": [\n        {\"name\":\"Alpha\",\"active\":true, \"monthly_revenue\":[10000,12000,11000]},\n        {\"name\":\"Beta\", \"active\":false,\"monthly_revenue\":[8000,9000,8500]},\n        {\"name\":\"Gamma\",\"active\":true, \"monthly_revenue\":[15000,16000,14000]}\n    ]\n}`\n\n// Total revenue across all active teams, flattening the per-team monthly arrays first\nfj.Get(teamData, `teams.@filter:{\"key\":\"active\",\"value\":true}|@pluck:monthly_revenue|@flatten|@sum`).Raw()\n// → 78000   (Alpha: 33000 + Gamma: 45000)\n```\n\n---\n\n**Example 5 — URL-slug generation from a display name**\n\n```go\n// Multi-word title with duplicate internal spaces → URL-safe kebab-case slug\nfj.Get(`\"My   Blog Post Title\"`, `@trim|@lowercase|@kebabcase`).Raw()\n// → \"my-blog-post-title\"\n\n// Author name to lowercase slug\nfj.Get(`\"John Doe\"`, `@lowercase|@replace:{\"target\":\" \",\"replacement\":\"-\"}`).Raw()\n// → \"john-doe\"\n```\n\n---\n\n**Example 6 — Config merging and introspection**\n\n```go\n// Merge two partial config objects; later values overwrite earlier ones for duplicate keys\noverrides := `[{\"host\":\"localhost\",\"port\":5432},{\"port\":5433,\"ssl\":true}]`\n\nmerged := fj.Get(overrides, `@join`).Raw()\n// → {\"host\":\"localhost\",\"port\":5433,\"ssl\":true}\n\n// Inspect which keys are present after the merge\nfj.Get(merged, `@keys`).Raw()\n// → [\"host\",\"port\",\"ssl\"]\n\n// Count the merged keys\nfj.Get(merged, `@count`).Raw()\n// → 3\n\n// Project only the connection-relevant subset and rename for the driver\nfj.Get(merged, `@project:{\"pick\":[\"host\",\"port\"],\"rename\":{\"port\":\"dbPort\"}}`).Raw()\n// → {\"host\":\"localhost\",\"dbPort\":5433}\n```\n\n---\n\n**Example 7 — Leaderboard: zip parallel arrays, filter, and pluck**\n\n```go\n// Two parallel arrays zipped via @group into an array-of-objects, then filtered and plucked\nleaderboard := `{\"player\":[\"Alice\",\"Bob\",\"Carol\",\"Dave\"],\"score\":[98,72,85,91]}`\n\n// Zip the parallel arrays into objects\ngrouped := fj.Get(leaderboard, `@group`).Raw()\n// → [{\"player\":\"Alice\",\"score\":98},{\"player\":\"Bob\",\"score\":72},\n//    {\"player\":\"Carol\",\"score\":85},{\"player\":\"Dave\",\"score\":91}]\n\n// Players with a score of 85 or above\nfj.Get(grouped, `@filter:{\"key\":\"score\",\"op\":\"gte\",\"value\":85}|@pluck:player`).Raw()\n// → [\"Alice\",\"Carol\",\"Dave\"]\n\n// Top player's full record\nfj.Get(grouped, `@filter:{\"key\":\"score\",\"op\":\"gte\",\"value\":95}|@first`).Raw()\n// → {\"player\":\"Alice\",\"score\":98}\n```\n\n---\n\n#### Registering custom transformers\n\n```go\nfunc init() {\n    fj.AddTransformer(\"redact\", fj.TransformerFunc(func(json, arg string) string {\n        return `\"[REDACTED]\"`\n    }))\n}\n\n// Usage in path\nfj.Get(json, \"user.password.@redact\").String() // \"[REDACTED]\"\n```\n\nTransformers can be disabled globally with `fj.DisableTransformers = true`.\n\n### Search and Scan Helpers\n\n```go\n// Full-tree substring search across all leaf values\nhits := response.SearchJSONBody(\"admin\")\n\n// Wildcard scan of leaf values\nhits = response.SearchJSONBodyMatch(\"err*\")\n\n// Find all values stored under specific key names\nemails := response.SearchJSONBodyByKey(\"email\")\n\n// Find all values under keys matching a wildcard\nhits = response.SearchJSONBodyByKeyPattern(\"user*\")\n\n// Substring / wildcard check at a specific path\nresponse.JSONBodyContains(\"user.role\", \"admin\")\nresponse.JSONBodyContainsMatch(\"user.email\", \"*@example.com\")\n\n// Return the dot-notation path where a value first appears\npath := response.FindJSONBodyPath(\"alice@example.com\")\n\n// All paths where value matches a pattern\npaths := response.FindJSONBodyPathsMatch(\"err*\")\n```\n\n### Data Manipulation Helpers\n\n```go\nimport \"github.com/sivaosorg/replify/pkg/fj\"\n\n// Count elements at a path\nn := response.CountJSONBody(\"items\")\n\n// Filter array elements by predicate\nactive := response.FilterJSONBody(\"users\", func(ctx fj.Context) bool {\n    return ctx.Get(\"active\").Bool()\n})\n\n// First match\nadmin := response.FirstJSONBody(\"users\", func(ctx fj.Context) bool {\n    return ctx.Get(\"role\").String() == \"admin\"\n})\n\n// Deduplicate (first-occurrence order preserved)\ntags := response.DistinctJSONBody(\"tags\")\n\n// Project fields from an array of objects\nrows := response.PluckJSONBody(\"users\", \"id\", \"email\")\n\n// Group by a key field\nbyRole := response.GroupByJSONBody(\"users\", \"role\")\n\n// Sort array by a field (numeric or string comparison)\nsorted := response.SortJSONBody(\"products\", \"price\", true)\n```\n\n### Limitations\n\n- **Read-only**: `fj` cannot write or modify JSON. Use `encoding/json` for serialization.\n- **No schema validation**: For strict schema enforcement use a dedicated library.\n- **No struct binding**: `fj` returns `Context` values, not typed Go structs. Use `encoding/json` when binding is required.\n- **`Raw()` lifetime**: The raw string returned by `Context.Raw()` is a zero-copy view into the source JSON string. It must not outlive the original string.\n- **`UnsafeBytes`**: The byte slice returned by `fj.UnsafeBytes` shares memory with the source string. Never mutate it, as this violates Go's string immutability guarantees and can cause undefined behavior.\n- **Malformed input**: `fj` does not validate JSON before parsing. Pass untrusted input through `fj.IsValidJSON` or `ValidJSONBody()` first.\n- **Transformers are global**: `AddTransformer` writes to a package-level registry. Register all transformers during program initialization (e.g., in `init()` functions) before concurrent access begins to avoid data races.\n\n### Best Practices\n\n1. **Check existence before use**\n\n   ```go\n   if ctx := response.QueryJSONBody(\"optional.key\"); ctx.Exists() {\n       process(ctx.String())\n   }\n   ```\n\n2. **Parse once, query many times**\n\n   ```go\n   doc := response.JSONBodyParser()\n   id    := doc.Get(\"user.id\").String()\n   email := doc.Get(\"user.email\").String()\n   role  := doc.Get(\"user.role\").String()\n   ```\n\n3. **Prefer `GetBytes` for byte-slice payloads**\n\n   ```go\n   // ✅ avoids string conversion allocation\n   ctx := fj.GetBytes(rawBytes, \"user.name\")\n\n   // ❌ unnecessary allocation\n   ctx = fj.Get(string(rawBytes), \"user.name\")\n   ```\n\n4. **Validate untrusted input first**\n\n   ```go\n   if !response.ValidJSONBody() {\n       return errors.New(\"invalid JSON body\")\n   }\n   ```\n\n5. **Register custom transformers in `init()`**\n\n   ```go\n   func init() {\n       fj.AddTransformer(\"mask\", func(json, arg string) string {\n           return `\"***\"`\n       })\n   }\n   ```\n\n6. **Never mutate `UnsafeBytes` output**\n\n   ```go\n   b := fj.UnsafeBytes(someString)\n   // ✅ read-only access\n   _ = b[0]\n   // ❌ mutating b corrupts the original string\n   ```\n\n## Contributing\n\nTo contribute to this project, follow these steps:\n\n1. **Clone the repository**\n   ```bash\n   git clone --depth 1 https://github.com/sivaosorg/replify.git\n   ```\n\n2. **Navigate to the project directory**\n   ```bash\n   cd replify\n   ```\n\n3. **Prepare the project environment**\n   ```bash\n   go mod tidy\n   ```\n\n4. **Make your changes**\n   - Follow Go best practices\n   - Add tests for new features\n   - Update documentation\n\n5. **Run tests**\n   ```bash\n   go test ./...\n   ```\n\n6. **Submit a pull request**\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Related Packages\n\nPart of the **replify** ecosystem:\n\n- [replify](https://github.com/sivaosorg/replify) - API response wrapping library (this package)\n- [conv](https://github.com/sivaosorg/replify/pkg/conv) - Type conversion utilities\n- [coll](https://github.com/sivaosorg/replify/pkg/coll) - Type-safe collection utilities\n- [common](https://github.com/sivaosorg/replify/pkg/common) - Reflection-based utilities\n- [encoding](https://github.com/sivaosorg/replify/pkg/encoding) - JSON encoding utilities\n- [hashy](https://github.com/sivaosorg/replify/pkg/hashy) - Deterministic hashing\n- [match](https://github.com/sivaosorg/replify/pkg/match) - Wildcard pattern matching\n- [msort](https://github.com/sivaosorg/replify/pkg/msort) - Map sorting utilities\n- [randn](https://github.com/sivaosorg/replify/pkg/randn) - Random data generation\n- [ref](https://github.com/sivaosorg/replify/pkg/ref) - Pointer utilities\n- [strutil](https://github.com/sivaosorg/replify/pkg/strutil) - String utilities\n- [truncate](https://github.com/sivaosorg/replify/pkg/truncate) - String truncation utilities\n\n## Support\n\n- **Issues**: [GitHub Issues](https://github.com/sivaosorg/replify/issues)\n- **Discussions**: [GitHub Discussions](https://github.com/sivaosorg/replify/discussions)\n\n## Acknowledgments\n\nBuilt with ❤️ for the Go community.","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsivaosorg%2Freplify","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsivaosorg%2Freplify","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsivaosorg%2Freplify/lists"}