fj

package
v0.1.24 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 13, 2026 License: GPL-3.0 Imports: 18 Imported by: 0

README

fj

fj (Fast JSON) is a Go package that provides a fast and simple way to retrieve, query, and transform values from a JSON document without unmarshalling the entire structure into Go types.

Overview

The fj package uses a dot-notation path syntax that supports wildcards, array indexing, conditional queries, multi-selectors, pipe operators, and a rich set of built-in transformers. Custom transformers can be registered at runtime without modifying the core library.

Key Features:

  • Zero-allocation hot pathsGet and GetBytes minimize heap pressure
  • 🔍 Rich path syntax — wildcards, array indexing, queries, multi-selectors, literals
  • 🔧 Built-in transformers — 28+ transformers including @pretty, @minify, @reverse, @flatten, @join, @snakeCase, @camelCase, and more
  • 🧩 Extensible — register custom transformers via AddTransformer
  • 🎨 JSON color output — 27 named color styles for terminal display
  • 🔒 Concurrency-safe — all public API functions are safe for concurrent goroutine use
  • 📁 File and stream support — parse JSON from files and io.Reader sources

Use Cases

When to Use
  • ✅ Extracting specific fields from large JSON documents without full unmarshalling
  • ✅ Building lightweight JSON pipelines with composable transformers
  • ✅ Quickly querying deeply nested or dynamic JSON structures
  • ✅ Colorizing JSON output for terminal applications
  • ✅ Validating JSON at the edge before heavy processing
When Not to Use
  • ❌ When you need full struct binding (use encoding/json or json-iterator)
  • ❌ When you need to write or modify JSON (use encoding/json)
  • ❌ When JSON schema validation is required (use a dedicated schema library)

Getting Started

Requirements

Go version 1.19 or higher.

Installation
go get github.com/sivaosorg/replify

Import the sub-package in your Go code:

import "github.com/sivaosorg/replify/pkg/fj"

Usage

Basic Field Access
package main

import (
    "fmt"
    "github.com/sivaosorg/replify/pkg/fj"
)

func main() {
    json := `{
        "user": {
            "name": "Alice",
            "age": 30,
            "active": true,
            "roles": ["Admin", "Editor"]
        }
    }`

    fmt.Println(fj.Get(json, "user.name").String())    // Alice
    fmt.Println(fj.Get(json, "user.age").Int64())      // 30
    fmt.Println(fj.Get(json, "user.active").Bool())    // true
    fmt.Println(fj.Get(json, "user.roles.0").String()) // Admin
    fmt.Println(fj.Get(json, "user.roles.#").Int64())  // 2 (array length)
}
Array Iteration
json := `{"roles":[{"name":"Admin"},{"name":"Editor"}]}`

// Collect all names
ctx := fj.Get(json, "roles.#.name")
fmt.Println(ctx.String()) // ["Admin","Editor"]

// Iterate with Foreach
fj.Get(json, "roles").Foreach(func(_, val fj.Context) bool {
    fmt.Println(val.Get("name").String())
    return true
})
Queries
json := `{"items":[
    {"name":"apple","price":1.2},
    {"name":"banana","price":0.8},
    {"name":"cherry","price":3.5}
]}`

// First item with price > 1.0
fmt.Println(fj.Get(json, `items.#(price>1.0).name`).String()) // apple

// All items with price > 1.0
fmt.Println(fj.Get(json, `items.#(price>1.0)#.name`).String()) // ["apple","cherry"]
Multi-Selectors
json := `{"id":1,"name":"Alice","email":"alice@example.com","age":30}`

// Build a new object with selected fields
fmt.Println(fj.Get(json, `{id,name}`).String())
// {"id":1,"name":"Alice"}

// Build a new array with selected fields
fmt.Println(fj.Get(json, `[id,name]`).String())
// [1,"Alice"]
Transformers
json := `{"message":"  Hello, World!  "}`

// Trim whitespace
fmt.Println(fj.Get(json, "message.@trim"))

// Convert to uppercase
fmt.Println(fj.Get(json, "message.@uppercase"))

// Pretty-print JSON
data := `{"b":2,"a":1}`
fmt.Println(fj.Get(data, "@pretty"))

// Pretty-print with sorted keys
fmt.Println(fj.Get(data, `@pretty:{"sort_keys":true}`))

// Minify
fmt.Println(fj.Get(data, "@minify"))
Working with Bytes
data := []byte(`{"status":"ok","code":200}`)
ctx := fj.GetBytes(data, "code")
fmt.Println(ctx.Int64()) // 200
Reading from a File
content, err := fj.ParseJSONFile("/path/to/data.json")
if err != nil {
    log.Fatal(err)
}
ctx := fj.Get(content, "user.name")
fmt.Println(ctx.String())
Validation
fmt.Println(fj.IsValidJSON(`{"key":"value"}`)) // true
fmt.Println(fj.IsValidJSON(`{bad json}`))       // false
Chaining
json := `{"a":{"b":{"c":"deep"}}}`
ctx := fj.Get(json, "a").Get("b").Get("c")
fmt.Println(ctx.String()) // deep
Working with Bytes (no-allocation)

When JSON is already in a []byte slice, use GetBytes to avoid converting to string. To further avoid converting ctx.Raw() back to []byte, use ctx.Index() as a zero-allocation sub-slice:

package main

import (
    "fmt"
    "github.com/sivaosorg/replify/pkg/fj"
)

var data = []byte(`{"user":{"id":"12345","roles":[{"roleId":"1","roleName":"Admin"},{"roleId":"2","roleName":"Editor"}]}}`)

func main() {
    ctx := fj.GetBytes(data, "user.roles.#.roleName")

    // Zero-allocation sub-slice of the original []byte
    var raw []byte
    if ctx.Index() > 0 {
        raw = data[ctx.Index() : ctx.Index()+len(ctx.Raw())]
    } else {
        raw = []byte(ctx.Raw())
    }
    fmt.Println(string(raw)) // ["Admin","Editor"]
}
Existence

Use Exists() to distinguish a missing path from an explicit JSON null:

package main

import (
    "fmt"
    "github.com/sivaosorg/replify/pkg/fj"
)

var data = []byte(`{"user":{"id":"12345","name":{"firstName":"John","lastName":"Doe"}}}`)

func main() {
    ctx := fj.GetBytes(data, "user.name.firstName")
    if ctx.Exists() {
        fmt.Println(ctx.String()) // John
    } else {
        fmt.Println("not found")
    }
}
Loop

Foreach iterates over array elements or object key-value pairs. Return false from the callback to stop early:

package main

import (
    "fmt"
    "github.com/sivaosorg/replify/pkg/fj"
)

var data = []byte(`{"user":{"roles":[{"roleId":"1","roleName":"Admin"},{"roleId":"2","roleName":"Editor"}]}}`)

func main() {
    fj.GetBytes(data, "user.roles").Foreach(func(_, value fj.Context) bool {
        fmt.Println(value.Get("roleName").String())
        return true
    })
    // Admin
    // Editor
}
Unmarshal

Use Value() to obtain a native Go type via a type assertion:

package main

import (
    "fmt"
    "github.com/sivaosorg/replify/pkg/fj"
)

var data = []byte(`{"user":{"id":12345,"name":{"firstName":"John","lastName":"Doe"}}}`)

func main() {
    // As map[string]interface{}
    name, ok := fj.GetBytes(data, "user.name").Value().(map[string]interface{})
    if ok {
        fmt.Println(name) // map[firstName:John lastName:Doe]
    }

    // As float64
    id, ok := fj.GetBytes(data, "user.id").Value().(float64)
    if ok {
        fmt.Println(id) // 12345
    }

    // Direct typed accessors are often more ergonomic
    fmt.Println(fj.GetBytes(data, "user.id").Int64()) // 12345
}
Parse & Get

ParseBytes parses JSON once; subsequent Get calls navigate inside the parsed result without re-scanning the entire document:

package main

import (
    "fmt"
    "github.com/sivaosorg/replify/pkg/fj"
)

var data = []byte(`{"user":{"id":12345,"name":{"firstName":"John","lastName":"Doe"}}}`)

func main() {
    // All three are equivalent
    fmt.Println(fj.ParseBytes(data).Get("user").Get("id").Int64()) // 12345
    fmt.Println(fj.GetBytes(data, "user.id").Int64())              // 12345
    fmt.Println(fj.GetBytes(data, "user").Get("id").Int64())       // 12345
}
JSON Lines (extended)

JSON Lines support uses the .. prefix to treat a multi-line document as an array. Requires JSON Lines format (one JSON object per line).

package main

import (
    "fmt"
    "github.com/sivaosorg/replify/pkg/fj"
)

var lines = []byte(`
    {"roleId":"1","roleName":"Admin","permissions":[{"permissionId":"101","permissionName":"View Reports"},{"permissionId":"102","permissionName":"Manage Users"}]}
    {"roleId":"2","roleName":"Editor","permissions":[{"permissionId":"201","permissionName":"Edit Content"},{"permissionId":"202","permissionName":"View Analytics"}]}
`)

func main() {
    // Count lines
    fmt.Println(fj.ParseBytes(lines).Get("..#").String()) // 2

    // Get a specific line by index
    fmt.Println(fj.GetBytes(lines, "..0").Get("roleName").String()) // Admin

    // Pluck a field from every line
    fmt.Println(fj.GetBytes(lines, "..#.roleName").String()) // ["Admin","Editor"]

    // Nested query across all lines
    fmt.Println(fj.GetBytes(lines, `..#.permissions.#(permissionId=="101").permissionName`).String())
    // ["View Reports"]
}

Path Syntax Reference

A fj path is a sequence of elements separated by .. In addition to ., several characters carry special meaning: |, #, @, \, *, !, and ?.

Syntax Description Example
field Object field access user.name
field.N Array index access (0-based) roles.0
field.# Array length roles.#3
field.#.key Pluck field from all elements roles.#.name["Admin","Editor"]
field.* Wildcard — matches any characters us*.name
field.? Wildcard — matches exactly one char us?.name
field\.key Escape special character . a\.b
field.#(k==v) Query: first match #(active==true).name
field.#(k==v)# Query: all matches #(price>1)#.name
k%"pat" Query: like (wildcard match) #(name%"A*")
k!%"pat" Query: not like #(name!%"A*")
~true / ~false / ~null / ~* Tilde — boolean coercion in queries #(active==~true)
a.b / a|b Dot and pipe separators user.name or user|name
{f1,f2} Multi-selector → new object {id,name}
[f1,f2] Multi-selector → new array [id,name]
!"value" JSON literal in multi-selector {"active":!true}
..field JSON Lines — query each line ..user.name
Object & Array Examples
# Basic field access
id                              → "http://subs/base-sample-schema.json"
properties.alias.description    → "An unique identifier in a submission."
properties.alias.minLength      → 1
required                        → ["alias","taxonId","releaseDate"]
required.0                      → "alias"
required.1                      → "taxonId"
oneOf.0.required.1              → "team"

# Wildcards (* matches any sequence, ? matches one character)
anim*ls.1.name                  → "Barky"
*nimals.1.name                  → "Barky"

# Escape special characters
properties.alias\.description   → "An unique identifier in a submission."

# Array length and pluck
animals.#                       → 3
animals.#.name                  → ["Meowsy","Barky","Purrpaws"]
Query Examples

Queries support ==, !=, <, <=, >, >=, % (like), and !% (not like):

stock.#(price_2002==56.27).symbol                → "MMM"
stock.#(company=="Amazon.com").symbol             → "AMZN"
stock.#(initial_price>=10)#.symbol               → ["MMM","AMZN","CPB","DIS","DOW","XOM","F","GPS","GIS"]
stock.#(company%"D*")#.symbol                    → ["DIS","DOW"]
stock.#(company!%"D*")#.symbol                   → ["MMM","AMZN","CPB","XOM","F","GPS","GIS"]
required.#(%"*as*")#                             → ["alias","releaseDate"]
required.#(%"*as*")                              → "alias"
animals.#(foods.likes.#(%"*a*"))#.name           → ["Meowsy","Barky"]
Tilde Operator

The ~ operator evaluates a value as a boolean before comparison. The most recent value that does not exist is treated as false:

~true    → interprets truthy values as true
~false   → interprets falsy and undefined values as true
~null    → interprets null and undefined values as true
~*       → interprets any defined value as true
bank.#(isActive==~true)#.name   → ["Davis Wade","Oneill Everett"]
bank.#(isActive==~false)#.name  → ["Stark Jenkins","Odonnell Rollins","Rachelle Chang","Dalton Waters"]
bank.#(eyeColor==~null)#.name   → ["Dalton Waters"]
bank.#(company==~*)#.name       → ["Stark Jenkins","Odonnell Rollins","Rachelle Chang","Davis Wade","Oneill Everett","Dalton Waters"]
Dot & Pipe

The . is the default separator; | can also be used. They behave identically except after # inside array/query contexts:

bank.0.balance                         → "$1,404.23"
bank|0.balance                         → "$1,404.23"
bank.0|balance                         → "$1,404.23"
bank.#                                 → 6
bank.#(gender=="female")#|#            → 2
bank.#(gender=="female")#.name         → ["Rachelle Chang","Davis Wade"]
bank.#(gender=="female")#|name         → not-present
bank.#(gender=="female")#|0            → first female object
Multi-Selectors

Comma-separated selectors inside {...} create a new object; inside [...] create a new array:

{version,author,type,"top":stock.#(price_2007>=10)#.symbol}
→ {"version":"1.0.0","author":"subs","type":"object","top":["MMM","AMZN","CPB","DIS","DOW","XOM","GPS","GIS"]}
Literals

JSON literals are prefixed with ! and are useful when building new objects with Multi-Selectors:

{version,author,"marked":!true,"scope":!"static"}
→ {"version":"1.0.0","author":"subs","marked":true,"scope":"static"}

Built-in Transformers

Transformers 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.

path.@transformerName
path.@transformerName:argument
path.@transformerName:{"key":"value"}
Core transformers
Transformer Alias(es) Input Description
@pretty any Pretty-print (indented) JSON. Accepts optional {"sort_keys":true,"indent":"\t","prefix":"","width":80}.
@minify @ugly any Compact single-line JSON (all whitespace removed).
@valid any Returns "true" / "false" — whether the input is valid JSON.
@this any Identity — returns the input unchanged.
@reverse array | object Reverses element order (array) or key order (object).
@flatten array Shallow-flatten nested arrays. Pass {"deep":true} to recurse.
@join array of objects Merge an array of objects into one object. Pass {"preserve":true} to keep duplicate keys.
@keys object Return a JSON array of the object's keys.
@values object Return a JSON array of the object's values.
@group object of arrays Zip object-of-arrays into an array-of-objects.
@search any @search:path — collect all values reachable at path anywhere in the tree.
@json string Parse the string as JSON and return the value.
@string any Encode the value as a JSON string literal.
String transformers
Transformer Alias(es) Description
@uppercase @upper Convert all characters to upper-case.
@lowercase @lower Convert all characters to lower-case.
@flip Reverse the characters of the string.
@trim Strip leading/trailing whitespace.
@snakecase @snake, @snakeCase Convert to snake_case.
@camelcase @camel, @camelCase Convert to camelCase.
@kebabcase @kebab, @kebabCase Convert to kebab-case.
@replace @replace:{"target":"old","replacement":"new"} — replace first occurrence.
@replaceAll @replaceAll:{"target":"old","replacement":"new"} — replace all occurrences.
@hex Hex-encode the value.
@bin Binary-encode the value.
@insertAt @insertAt:{"index":5,"insert":"XYZ"} — insert a substring at position.
@wc Return the word-count of a string as an integer.
@padLeft @padLeft:{"padding":"*","length":10} — left-pad to a fixed width.
@padRight @padRight:{"padding":"*","length":10} — right-pad to a fixed width.
Object transformers
Transformer Description
@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.
@default Inject fallback values for fields that are absent or null. Arg: {"field":"defaultValue",...}. Existing non-null fields are never overwritten.
Array transformers
Transformer Description
@filter Keep only elements matching a condition. Arg: {"key":"field","op":"eq","value":val}. Operators: eq (default), ne, gt, gte, lt, lte, contains.
@pluck Extract a named field (supports dot-notation paths) from every element. Arg: field path string, e.g. @pluck:name or @pluck:addr.city.
@first Return the first element of the array, or null if empty.
@last Return the last element of the array, or null if empty.
@count Return the number of elements (array) or key-value pairs (object) as an integer. Scalars return 0.
@sum Sum all numeric values in the array; non-numeric elements are skipped. Returns 0 for empty arrays.
@min Return the minimum numeric value in the array. Returns null when no numbers are present.
@max Return the maximum numeric value in the array. Returns null when no numbers are present.
Value normalization transformers
Transformer Description
@coerce Convert a scalar to a target type. Arg: {"to":"string"}, {"to":"number"}, or {"to":"bool"}. Objects and arrays are returned unchanged.
Transformer Examples
package main

import (
	"fmt"
	"github.com/sivaosorg/replify/pkg/fj"
)

func main() {
	json := `{
    "user": {"name": "Alice", "role": null, "age": 30, "city": "NY"},
    "scores": [95, 87, 92, 78],
    "users": [
        {"name": "Alice", "active": true,  "addr": {"city": "NY"}},
        {"name": "Bob",   "active": false, "addr": {"city": "LA"}},
        {"name": "Carol", "active": true,  "addr": {"city": "NY"}}
    ]
}`

	// ── Core ─────────────────────────────────────────────────────────────────────
	fmt.Println(fj.Get(json, "@pretty").String())      // indented JSON
	fmt.Println(fj.Get(json, "@minify").String())      // compact JSON
	fmt.Println(fj.Get(json, "user.@keys").String())   // ["name","role","age","city"]
	fmt.Println(fj.Get(json, "user.@values").String()) // ["Alice",null,30,"NY"]
	fmt.Println(fj.Get(json, "user.@valid").String())  // "true"

	// ── String ───────────────────────────────────────────────────────────────────
	fmt.Println(fj.Get(json, "user.name.@uppercase").String())                                // "ALICE"
	fmt.Println(fj.Get(json, "user.name.@reverse").String())                                  // "ecilA"
	fmt.Println(fj.Get(json, "user.name.@snakecase").String())                                // "alice"
	fmt.Println(fj.Get(json, "user.city.@padLeft:{\"padding\":\"0\",\"length\":6}").String()) // "000 NY"

	// ── Object ───────────────────────────────────────────────────────────────────

	// Project: keep only name and age, rename age → years
	fmt.Println(fj.Get(json, `user.@project:{"pick":["name","age"],"rename":{"age":"years"}}`).Raw())
	// → {"name":"Alice","years":30}

	// Default: fill in missing / null fields
	fmt.Println(fj.Get(json, `user.@default:{"role":"viewer","active":true}`).Raw())
	// → {"name":"Alice","role":"viewer","age":30,"city":"NY","active":true}

	// ── Array ────────────────────────────────────────────────────────────────────

	// Filter: keep only active users
	fmt.Println(fj.Get(json, `users.@filter:{"key":"active","value":true}`).Raw())
	// → [{"name":"Alice","active":true,...},{"name":"Carol","active":true,...}]

	// Pluck: extract the city from every user's address
	fmt.Println(fj.Get(json, `users.@pluck:addr.city`).Raw())
	// → ["NY","LA","NY"]

	// Aggregation helpers
	fmt.Println(fj.Get(json, "scores.@first").Raw()) // 95
	fmt.Println(fj.Get(json, "scores.@last").Raw())  // 78
	fmt.Println(fj.Get(json, "scores.@count").Raw()) // 4
	fmt.Println(fj.Get(json, "scores.@sum").Raw())   // 352
	fmt.Println(fj.Get(json, "scores.@min").Raw())   // 78
	fmt.Println(fj.Get(json, "scores.@max").Raw())   // 95

	// ── Coerce ───────────────────────────────────────────────────────────────────
	fmt.Println(fj.Get(`42`, `@coerce:{"to":"string"}`).Raw())   // "42"
	fmt.Println(fj.Get(`"99"`, `@coerce:{"to":"number"}`).Raw()) // 99
	fmt.Println(fj.Get(`1`, `@coerce:{"to":"bool"}`).Raw())      // true
}
Composing transformers

Transformers can be chained using the | pipe operator or dot notation:

	// First filter the array, then count the remaining elements
	fmt.Println(fj.Get(json, `users.@filter:{"key":"active","value":true}|@count`).Raw())
	// → 2

	// Pluck names, then reverse the resulting array
	fmt.Println(fj.Get(json, `users.@pluck:name|@reverse`).Raw())
	// → ["Carol","Bob","Alice"]
Complex real-world examples

The following scenarios demonstrate how to combine multiple transformers into a single expression to process realistic JSON payloads.


Example 1 — E-commerce product catalog: filter, aggregate, and shape

package main

import (
	"fmt"
	"github.com/sivaosorg/replify/pkg/fj"
)

func main() {
	catalog := `{
    "products": [
        {"id":"p1","name":"Laptop Pro",    "category":"electronics","price":1299.99,"stock":5},
        {"id":"p2","name":"USB-C Hub",     "category":"electronics","price":49.99,  "stock":120},
        {"id":"p3","name":"Desk Chair",    "category":"furniture",  "price":349.00, "stock":0},
        {"id":"p4","name":"Standing Desk", "category":"furniture",  "price":699.00, "stock":3},
        {"id":"p5","name":"Webcam HD",     "category":"electronics","price":89.99,  "stock":45}
    ]
}`

	// All in-stock electronics names
	fmt.Println(fj.Get(catalog, `products.@filter:{"key":"category","value":"electronics"}|@filter:{"key":"stock","op":"gt","value":0}|@pluck:name`).Raw())
	// → ["Laptop Pro","USB-C Hub","Webcam HD"]

	// Count of in-stock products
	fmt.Println(fj.Get(catalog, `products.@filter:{"key":"stock","op":"gt","value":0}|@count`).Raw())
	// → 4

	// Price range of in-stock products
	fmt.Println(fj.Get(catalog, `products.@filter:{"key":"stock","op":"gt","value":0}|@pluck:price|@min`).Raw())
	// → 49.99
	fmt.Println(fj.Get(catalog, `products.@filter:{"key":"stock","op":"gt","value":0}|@pluck:price|@max`).Raw())
	// → 1299.99

	// Project the first in-stock product as a display card (pick and rename fields)
	first := fj.Get(catalog, `products.@filter:{"key":"stock","op":"gt","value":0}|@first`).Raw()
	fmt.Println(fj.Get(first, `@project:{"pick":["name","price"],"rename":{"name":"title","price":"cost"}}`).Raw())
	// → {"title":"Laptop Pro","cost":1299.99}
}

Example 2 — API response normalization: fill defaults then project and rename

	// Raw user record from an external API with null / absent fields
	rawUser := `{"id":"u1","name":"Alice","role":null,"verified":null}`

	// One-shot normalization: fill nulls → keep only safe fields → rename id for the frontend
	fmt.Println(fj.Get(rawUser, `@default:{"role":"viewer","verified":false}|@project:{"pick":["id","name","role","verified"],"rename":{"id":"userId"}}`).Raw())
	// → {"userId":"u1","name":"Alice","role":"viewer","verified":false}

Example 3 — Log processing: filter, count, and retrieve the latest entry

	logs := `[
    {"level":"error","msg":"Connection refused","ts":1700001},
    {"level":"info", "msg":"Server started",    "ts":1700002},
    {"level":"error","msg":"Timeout exceeded",  "ts":1700003},
    {"level":"warn", "msg":"High memory",       "ts":1700004}
]`

	// How many errors?
	fmt.Println(fj.Get(logs, `@filter:{"key":"level","value":"error"}|@count`).Raw())
	// → 2

	// All error messages
	fmt.Println(fj.Get(logs, `@filter:{"key":"level","value":"error"}|@pluck:msg`).Raw())
	// → ["Connection refused","Timeout exceeded"]

	// Most recent error entry (last in the filtered array)
	fmt.Println(fj.Get(logs, `@filter:{"key":"level","value":"error"}|@last`).Raw())
	// → {"level":"error","msg":"Timeout exceeded","ts":1700003}

Example 4 — Nested data aggregation: filter → pluck → flatten → sum

	teamData := `{
    "teams": [
        {"name":"Alpha","active":true, "monthly_revenue":[10000,12000,11000]},
        {"name":"Beta", "active":false,"monthly_revenue":[8000,9000,8500]},
        {"name":"Gamma","active":true, "monthly_revenue":[15000,16000,14000]}
    ]
}`

	// Total revenue across all active teams, flattening the per-team monthly arrays first
	fmt.Println(fj.Get(teamData, `teams.@filter:{"key":"active","value":true}|@pluck:monthly_revenue|@flatten|@sum`).Raw())
	// → 78000   (Alpha: 33000 + Gamma: 45000)

Example 5 — URL-slug generation from a display name

	// Multi-word title with duplicate internal spaces → URL-safe kebab-case slug
	fmt.Println(fj.Get(`"My   Blog Post Title"`, `@trim|@lowercase|@kebabcase`).Raw())
	// → "my-blog-post-title"

	// Author name to lowercase slug
	fmt.Println(fj.Get(`"John Doe"`, `@lowercase|@replace:{"target":" ","replacement":"-"}`).Raw())
	// → "john-doe"

Example 6 — Config merging and introspection

	// Merge two partial config objects; later values overwrite earlier ones for duplicate keys
	overrides := `[{"host":"localhost","port":5432},{"port":5433,"ssl":true}]`

	merged := fj.Get(overrides, `@join`).Raw()
	// → {"host":"localhost","port":5433,"ssl":true}
	fmt.Println(merged)

	// Inspect which keys are present after the merge
	fmt.Println(fj.Get(merged, `@keys`).Raw())
	// → ["host","port","ssl"]

	// Count the merged keys
	fmt.Println(fj.Get(merged, `@count`).Raw())
	// → 3

	// Project only the connection-relevant subset and rename for the driver
	fmt.Println(fj.Get(merged, `@project:{"pick":["host","port"],"rename":{"port":"dbPort"}}`).Raw())
	// → {"host":"localhost","dbPort":5433}

Example 7 — Leaderboard: zip parallel arrays, filter, and pluck

	// Two parallel arrays zipped via @group into an array-of-objects, then filtered and plucked
	leaderboard := `{"player":["Alice","Bob","Carol","Dave"],"score":[98,72,85,91]}`

	// Zip the parallel arrays into objects
	grouped := fj.Get(leaderboard, `@group`).Raw()
	// → [{"player":"Alice","score":98},{"player":"Bob","score":72},
	//    {"player":"Carol","score":85},{"player":"Dave","score":91}]
	fmt.Println(grouped)

	// Players with a score of 85 or above
	fmt.Println(fj.Get(grouped, `@filter:{"key":"score","op":"gte","value":85}|@pluck:player`).Raw())
	// → ["Alice","Carol","Dave"]

	// Top player's full record
	fmt.Println(fj.Get(grouped, `@filter:{"key":"score","op":"gte","value":95}|@first`).Raw())
	// → {"player":"Alice","score":98}

Transformers can be disabled globally with fj.DisableTransformers = true.

Custom Transformers

Register a custom transformer with AddTransformer. Registrations are thread-safe and take effect immediately for all subsequent calls.

package main

import (
    "fmt"
    "strings"
    "github.com/sivaosorg/replify/pkg/fj"
)

func init() {
	// @shout appends "!!!" to the string value
	fj.AddTransformer("shout", fj.TransformerFunc(func(json, arg string) string {
		return strings.Trim(json, `"`) + "!!!"
	}))

	// @repeat repeats the value N times (arg is the count as a string)
	fj.AddTransformer("repeat", fj.TransformerFunc(func(json, arg string) string {
		n := fj.Parse(arg).Int64()
		if n <= 0 {
			return json
		}
		v := strings.Trim(json, `"`)
		return strings.Repeat(v, int(n))
	}))
}

func main() {
    json := `{"greeting":"hello"}`

    fmt.Println(fj.Get(json, "greeting.@shout"))
    // hello!!!

    fmt.Println(fj.Get(json, "greeting.@repeat:3"))
    // hellohellohello
}

JSON Color Styles

Use Context.StringColored() for default ANSI coloring or Context.WithStringColored(style) for a custom style.

ctx := fj.Get(json, "user")
fmt.Println(ctx.StringColored())                         // default style
fmt.Println(ctx.WithStringColored(fj.NeonStyle))         // neon style
fmt.Println(ctx.WithStringColored(fj.DarkStyle))         // dark style

Available named style variables:

Variable Description
DarkStyle Dark tones — navy blue, dark green, amber, dark magenta
NeonStyle Vibrant neon — bright cyan, lime, yellow
PastelStyle Soft pastel tones
HighContrastStyle High-contrast for accessibility
VintageStyle Muted vintage palette
CyberpunkStyle Futuristic cyberpunk neons
OceanStyle Cool ocean blues and cyans
FieryStyle Warm reds and oranges
GalaxyStyle Deep-space purples and blues
SunsetStyle Warm oranges and pinks
JungleStyle Lush jungle greens
MonochromeStyle Grayscale only
ForestStyle Earthy forest greens and browns
IceStyle Cold icy blues and whites
RetroStyle Retro terminal amber/green
AutumnStyle Browns, oranges, and reds
GothicStyle Dark purples and blacks
VaporWaveStyle Aesthetic vaporwave pinks and purples
VampireStyle Deep blood reds and blacks
CarnivalStyle Bright carnival multicolor
SteampunkStyle Brass and copper tones
WoodlandStyle Natural woodland tans and greens
CandyStyle Bright candy pastels
TwilightStyle Dusk purples and navies
EarthStyle Warm earth tones
ElectricStyle Electric blues and greens
WitchingHourStyle Dark witching-hour palette
MidnightStyle Deep midnight navy and silver

API Reference

Top-level Functions
Function Signature Description
Get Get(json, path string) Context Search JSON for a dot-notation path; return first match.
GetBytes GetBytes(json []byte, path string) Context Same as Get but accepts a byte slice.
Parse Parse(json string) Context Parse a JSON string into a Context without path querying.
ParseBytes ParseBytes(json []byte) Context Same as Parse but accepts a byte slice.
ParseReader ParseReader(in io.Reader) (string, error) Read all data from an io.Reader and return as a string.
ParseJSONFile ParseJSONFile(filepath string) (string, error) Read a JSON file and return its contents as a string.
IsValidJSON IsValidJSON(json string) bool Report whether a string is valid JSON.
IsValidJSONBytes IsValidJSONBytes(json []byte) bool Report whether a byte slice is valid JSON.
AddTransformer AddTransformer(name string, fn func(json, arg string) string) Register a named transformer.
Search Engine Functions
Function Signature Description
Search Search(json, keyword string) []Context Full-tree scan — return all scalar leaves whose string value contains keyword.
SearchMatch SearchMatch(json, pattern string) []Context Full-tree wildcard scan — return all scalar leaves whose string value matches pattern (*, ?). Uses match.Match.
SearchByKey SearchByKey(json string, keys ...string) []Context Return all values stored under the given key name(s) at any depth (exact names).
SearchByKeyPattern SearchByKeyPattern(json, keyPattern string) []Context Return all values stored under object keys that match the wildcard keyPattern. Uses match.Match.
Contains Contains(json, path, target string) bool Report whether the value at path contains the substring target.
ContainsMatch ContainsMatch(json, path, pattern string) bool Report whether the value at path matches the wildcard pattern. Uses match.Match.
FindPath FindPath(json, value string) string Return the first dot-notation path at which a scalar equals value.
FindPaths FindPaths(json, value string) []string Return all dot-notation paths at which a scalar equals value.
FindPathMatch FindPathMatch(json, valuePattern string) string Return the first dot-notation path at which a scalar matches the wildcard valuePattern.
FindPathsMatch FindPathsMatch(json, valuePattern string) []string Return all paths at which a scalar matches the wildcard valuePattern.
Count Count(json, path string) int Count elements at path (array length or 1 for scalars; 0 when missing).
Sum Sum(json, path string) float64 Sum of all numeric values at path.
Min Min(json, path string) (float64, bool) Minimum numeric value at path.
Max Max(json, path string) (float64, bool) Maximum numeric value at path.
Avg Avg(json, path string) (float64, bool) Arithmetic mean of numeric values at path.
CollectFloat64 CollectFloat64(json, path string) []float64 Collect numeric values at path using conv.Float64 (handles string-encoded numbers).
Filter Filter(json, path string, fn func(Context) bool) []Context Keep only elements at path for which fn returns true.
First First(json, path string, fn func(Context) bool) Context First element at path for which fn returns true.
Distinct Distinct(json, path string) []Context Unique values at path (first-occurrence order).
Pluck Pluck(json, path string, fields ...string) []Context Extract named fields from each object in the array at path.
GroupBy GroupBy(json, path, keyField string) map[string][]Context Group array elements by the string value of keyField, using conv.String for key normalization.
SortBy SortBy(json, path, keyField string, ascending bool) []Context Sort array elements by a sub-field using conv-powered numeric and string comparison.
CoerceTo CoerceTo(ctx Context, into any) error Coerce a Context's value into any Go typed variable via conv.Infer.
Context Methods
Method Signature Description
Kind Kind() Type Return the JSON type (Null, False, Number, String, True, JSON).
Raw Raw() string Return the raw unprocessed JSON fragment.
Number Number() float64 Return the numeric value (for Number type).
Index Index() int Return the byte offset of this value in the original JSON.
Indexes Indexes() []int Return positions of all #-matched elements.
String String() string Return a string representation of the value.
StringColored StringColored() string Return the string with default ANSI color styling.
WithStringColored WithStringColored(style *unify4g.Style) string Return the string with a custom ANSI color style.
Bool Bool() bool Return the boolean value.
Int64 Int64() int64 Return the value as int64.
Uint64 Uint64() uint64 Return the value as uint64.
Float64 Float64() float64 Return the value as float64.
Float32 Float32() float32 Return the value as float32.
Time Time() time.Time Parse the value as time.Time using RFC 3339.
WithTime WithTime(layout string) time.Time Parse the value as time.Time with a custom layout.
Array Array() []Context Return all array elements as a []Context.
IsObject IsObject() bool Report whether the value is a JSON object.
IsArray IsArray() bool Report whether the value is a JSON array.
IsBool IsBool() bool Report whether the value is a JSON boolean.
Exists Exists() bool Report whether the path was found in the JSON.
Value Value() interface{} Return the value as a native Go type (map, []interface{}, etc.).
Map Map() map[string]Context Return the value as a map[string]Context (for JSON objects).
Foreach Foreach(iterator func(key, value Context) bool) Iterate over array elements or object key-value pairs.
Get Get(path string) Context Query a sub-path (enables chaining).
GetMulti GetMulti(paths ...string) []Context Query multiple paths simultaneously.
Path Path(json string) string Return the dot-notation path that produced this context.
Paths Paths(json string) []string Return paths for each element in an array result.
Less Less(token Context, caseSensitive bool) bool Report whether this value is less than token.
JSON Type Constants
Constant Description
Null JSON null
False JSON false
Number JSON number
String JSON string
True JSON true
JSON JSON object or array

Examples

1. Nested Object Access
json := `{"store":{"book":{"title":"Go Programming","price":29.99}}}`
title := fj.Get(json, "store.book.title").String()
price := fj.Get(json, "store.book.price").Float64()
fmt.Printf("%s costs $%.2f\n", title, price) // Go Programming costs $29.99
2. Array Queries
json := `{"users":[
    {"name":"Alice","role":"admin","active":true},
    {"name":"Bob","role":"user","active":false},
    {"name":"Carol","role":"admin","active":true}
]}`

// All active admin names
admins := fj.Get(json, `users.#(role=="admin")#.name`)
fmt.Println(admins.String()) // ["Alice","Carol"]
3. Transformer Pipeline
json := `{"tags":["Go","json","fast"]}`
result := fj.Get(json, "tags.@reverse")
fmt.Println(result.String()) // ["fast","json","Go"]

// Chain transformers using pipe
reversed := fj.Get(json, "tags.@reverse|0")
fmt.Println(reversed.String()) // "fast"
4. Multi-Selector with Literals
json := `{"version":"1.0","author":"Alice","items":[1,2,3]}`
result := fj.Get(json, `{version,author,"count":items.#,"published":!true}`)
fmt.Println(result.String())
// {"version":"1.0","author":"Alice","count":3,"published":true}
5. Struct Unmarshalling via Value()
json := `{"name":"Alice","scores":[95,87,91]}`
m := fj.Parse(json).Map()
name := m["name"].String()
scores := m["scores"].Array()
fmt.Println(name)                           // Alice
fmt.Println(len(scores), scores[0].Int64()) // 3  95
6. Search Engine
package main

import (
    "fmt"
    "github.com/sivaosorg/replify/pkg/fj"
)

func main() {
    json := `{
        "store": {
            "books": [
                {"id":1,"title":"The Go Programming Language","author":"Donovan","price":34.99,"genre":"tech"},
                {"id":2,"title":"Clean Code","author":"Martin","price":29.99,"genre":"tech"},
                {"id":3,"title":"Harry Potter","author":"Rowling","price":14.99,"genre":"fiction"},
                {"id":4,"title":"Dune","author":"Herbert","price":12.99,"genre":"fiction"}
            ],
            "owner": "Alice"
        },
        "ratings": [5, 3, 4, 5, 1, 4],
        "tags": ["go","json","fast","go","json"]
    }`

    // --- Full-tree substring search ---
    matches := fj.Search(json, "tech")
    fmt.Println(len(matches)) // 2

    // --- Full-tree wildcard search (match.Match) ---
    wildMatches := fj.SearchMatch(json, "D*")
    fmt.Println(len(wildMatches)) // 2: "Donovan", "Dune"

    // --- Search by exact key name ---
    authors := fj.SearchByKey(json, "author")
    for _, a := range authors {
        fmt.Println(a.String()) // Donovan, Martin, Rowling, Herbert
    }

    // --- Search by key wildcard pattern (match.Match) ---
    keyMatches := fj.SearchByKeyPattern(json, "auth*")
    fmt.Println(len(keyMatches)) // 4 (author fields)

    // --- Substring contains ---
    fmt.Println(fj.Contains(json, "store.owner", "Ali")) // true

    // --- Wildcard contains (match.Match) ---
    fmt.Println(fj.ContainsMatch(json, "store.owner", "Al*")) // true

    // --- Path discovery (exact) ---
    fmt.Println(fj.FindPath(json, "Rowling")) // store.books.2.author

    // --- Path discovery (wildcard, match.Match) ---
    fmt.Println(fj.FindPathMatch(json, "Row*")) // store.books.2.author

    // --- All paths matching a wildcard ---
    paths := fj.FindPathsMatch(json, "D*")
    fmt.Println(paths) // [store.books.0.author store.books.3.title]

    // --- Aggregate functions ---
    fmt.Println(fj.Sum(json, "ratings"))       // 22
    fmt.Println(fj.Count(json, "store.books")) // 4
    v, _ := fj.Min(json, "ratings")
    fmt.Println(v) // 1
    v, _ = fj.Max(json, "ratings")
    fmt.Println(v) // 5
    avg, _ := fj.Avg(json, "ratings")
    fmt.Printf("%.4f\n", avg) // 3.6667

    // --- Collect numbers with conv.Float64 (handles string-encoded numbers too) ---
    nums := fj.CollectFloat64(json, "ratings")
    fmt.Println(nums) // [5 3 4 5 1 4]

    // --- Filter ---
    fiction := fj.Filter(json, "store.books", func(ctx fj.Context) bool {
        return ctx.Get("genre").String() == "fiction"
    })
    fmt.Println(len(fiction)) // 2

    // --- First ---
    cheap := fj.First(json, "store.books", func(ctx fj.Context) bool {
        return ctx.Get("price").Float64() < 20
    })
    fmt.Println(cheap.Get("title").String()) // Harry Potter

    // --- Distinct ---
    unique := fj.Distinct(json, "tags")
    fmt.Println(len(unique)) // 3: go, json, fast

    // --- Pluck ---
    projected := fj.Pluck(json, "store.books", "id", "title")
    for _, p := range projected {
        fmt.Println(p.String())
    }

    // --- GroupBy (uses conv.String for key normalization) ---
    groups := fj.GroupBy(json, "store.books", "genre")
    fmt.Println(len(groups["tech"]))    // 2
    fmt.Println(len(groups["fiction"])) // 2

    // --- SortBy (uses conv.Float64/conv.String for comparison) ---
    byPrice := fj.SortBy(json, "store.books", "price", true)
    fmt.Println(byPrice[0].Get("title").String()) // Dune (cheapest)

    // --- CoerceTo (uses conv.Infer) ---
    ctx := fj.Get(json, "store.books.0.price")
    var price float64
    if err := fj.CoerceTo(ctx, &price); err == nil {
        fmt.Printf("%.2f\n", price) // 34.99
    }
}

Best Practices

✅ Do's
  1. Check Exists() before using a value to distinguish a missing path from a JSON null:

    if ctx := fj.Get(json, "user.email"); ctx.Exists() {
        sendEmail(ctx.String())
    }
    
  2. Use GetBytes when JSON is already []byte to avoid an extra allocation:

    ctx := fj.GetBytes(rawBytes, "user.id")
    
  3. Register custom transformers once at startup (e.g., in init()) to avoid race conditions:

    func init() {
        fj.AddTransformer("myTransform", myFn)
    }
    
  4. Use Foreach instead of Array() when you only need to process elements one at a time:

    fj.Get(json, "items").Foreach(func(_, val fj.Context) bool {
        process(val)
        return true
    })
    
  5. Validate untrusted input with IsValidJSON before heavy processing:

    if !fj.IsValidJSON(input) {
        return errors.New("invalid JSON")
    }
    
❌ Don'ts
  1. Don't assume a zero-value Context means JSON null — it may mean the path was not found:

    // ❌ Bad
    val := fj.Get(json, "missing.field").String() // returns "" even if not null
    
    // ✅ Good
    if ctx := fj.Get(json, "missing.field"); ctx.Exists() {
        val := ctx.String()
    }
    
  2. Don't mutate the []byte passed to GetBytes while a query is in progress.

  3. Don't use @replace when you intend to replace all occurrences — use @replaceAll instead:

    // @replace replaces only the FIRST occurrence
    fj.Get(`"foo foo foo"`, `@replace:{"target":"foo","replacement":"bar"}`)
    // → bar foo foo
    
    // @replaceAll replaces ALL occurrences
    fj.Get(`"foo foo foo"`, `@replaceAll:{"target":"foo","replacement":"bar"}`)
    // → bar bar bar
    
  4. Don't call Map() on a non-object without first checking IsObject().

  5. Don't call Array() on a non-array without first checking IsArray().

Thread Safety

All public API functions (Get, GetBytes, Parse*, IsValid*, AddTransformer) are safe for concurrent use by multiple goroutines. The transformer registry uses a sync.RWMutex for safe concurrent reads and writes.

Contributing

To contribute to this project, follow these steps:

  1. Clone the repository:

    git clone --depth 1 https://github.com/sivaosorg/replify.git
    
  2. Navigate to the project directory:

    cd replify
    
  3. Prepare the project environment:

    go mod tidy
    
  4. Run the tests:

    go test ./pkg/fj/...
    
  5. Submit a pull request.

Documentation

Overview

Package fj (Fast JSON) provides a fast and simple way to retrieve, query, and transform values from a JSON document without unmarshalling the entire structure into Go types.

fj uses a dot-notation path syntax supporting wildcards, array indexing, conditional queries, multi-selectors, pipe operators, and a rich set of built-in transformers. Custom transformers can be registered at runtime.

Path Syntax

user.name              → object field access
roles.0.name           → array index access
roles.#.name           → iterate all array elements
roles.#(roleName=="Admin").roleId  → conditional query
name.@uppercase        → built-in transformer
name.@word:upper       → transformer with argument
{id,name}              → multi-selector (new object)
[id,name]              → multi-selector (new array)

Basic Usage

ctx := fj.Get(json, "user.roles.#.roleName")
fmt.Println(ctx.String()) // ["Admin","Editor"]

Custom Transformers

fj.AddTransformer("word", func(json, arg string) string {
    if arg == "upper" { return strings.ToUpper(json) }
    return json
})

fj is safe for concurrent use by multiple goroutines.

Index

Constants

This section is empty.

Variables

View Source
var (

	// DarkStyle uses darker tones for styling.
	DarkStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;25m", "\033[0m"},
		String:   [2]string{"\033[38;5;34m", "\033[0m"},
		Number:   [2]string{"\033[38;5;178m", "\033[0m"},
		True:     [2]string{"\033[38;5;127m", "\033[0m"},
		False:    [2]string{"\033[38;5;127m", "\033[0m"},
		Null:     [2]string{"\033[38;5;127m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;124m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;245m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// NeonStyle is a vibrant style using neon-like colors.
	NeonStyle = &encoding.Style{
		Key:      [2]string{"\033[1;96m", "\033[0m"},
		String:   [2]string{"\033[1;92m", "\033[0m"},
		Number:   [2]string{"\033[1;93m", "\033[0m"},
		True:     [2]string{"\033[1;95m", "\033[0m"},
		False:    [2]string{"\033[1;95m", "\033[0m"},
		Null:     [2]string{"\033[1;95m", "\033[0m"},
		Escape:   [2]string{"\033[1;91m", "\033[0m"},
		Brackets: [2]string{"\033[1;97m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// PastelStyle applies softer colors for a subdued look.
	PastelStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;152m", "\033[0m"},
		String:   [2]string{"\033[38;5;121m", "\033[0m"},
		Number:   [2]string{"\033[38;5;180m", "\033[0m"},
		True:     [2]string{"\033[38;5;139m", "\033[0m"},
		False:    [2]string{"\033[38;5;139m", "\033[0m"},
		Null:     [2]string{"\033[38;5;139m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;167m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;253m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// HighContrastStyle uses bold and contrasting colors for better visibility.
	HighContrastStyle = &encoding.Style{
		Key:      [2]string{"\033[1;37;44m", "\033[0m"},
		String:   [2]string{"\033[1;37;42m", "\033[0m"},
		Number:   [2]string{"\033[1;37;43m", "\033[0m"},
		True:     [2]string{"\033[1;37;45m", "\033[0m"},
		False:    [2]string{"\033[1;37;45m", "\033[0m"},
		Null:     [2]string{"\033[1;37;45m", "\033[0m"},
		Escape:   [2]string{"\033[1;37;41m", "\033[0m"},
		Brackets: [2]string{"\033[1;30;47m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// VintageStyle uses muted tones reminiscent of old terminal displays.
	VintageStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;94m", "\033[0m"},
		String:   [2]string{"\033[38;5;130m", "\033[0m"},
		Number:   [2]string{"\033[38;5;136m", "\033[0m"},
		True:     [2]string{"\033[38;5;95m", "\033[0m"},
		False:    [2]string{"\033[38;5;95m", "\033[0m"},
		Null:     [2]string{"\033[38;5;95m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;124m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;242m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// CyberpunkStyle mimics a futuristic neon cyberpunk aesthetic.
	CyberpunkStyle = &encoding.Style{
		Key:      [2]string{"\033[1;35;45m", "\033[0m"},
		String:   [2]string{"\033[1;36;46m", "\033[0m"},
		Number:   [2]string{"\033[1;33;43m", "\033[0m"},
		True:     [2]string{"\033[1;32;42m", "\033[0m"},
		False:    [2]string{"\033[1;31;41m", "\033[0m"},
		Null:     [2]string{"\033[1;37;40m", "\033[0m"},
		Escape:   [2]string{"\033[1;31;41m", "\033[0m"},
		Brackets: [2]string{"\033[1;30;47m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// OceanStyle is inspired by oceanic hues and soft contrasts.
	OceanStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;27m", "\033[0m"},
		String:   [2]string{"\033[38;5;45m", "\033[0m"},
		Number:   [2]string{"\033[38;5;33m", "\033[0m"},
		True:     [2]string{"\033[38;5;77m", "\033[0m"},
		False:    [2]string{"\033[38;5;77m", "\033[0m"},
		Null:     [2]string{"\033[38;5;67m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;196m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;15m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// FieryStyle uses intense warm colors like flames.
	FieryStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;166m", "\033[0m"},
		String:   [2]string{"\033[38;5;202m", "\033[0m"},
		Number:   [2]string{"\033[38;5;220m", "\033[0m"},
		True:     [2]string{"\033[38;5;214m", "\033[0m"},
		False:    [2]string{"\033[38;5;160m", "\033[0m"},
		Null:     [2]string{"\033[38;5;196m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;124m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;244m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// GalaxyStyle uses space-themed colors with a starry effect.
	GalaxyStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;57m", "\033[0m"},
		String:   [2]string{"\033[38;5;93m", "\033[0m"},
		Number:   [2]string{"\033[38;5;141m", "\033[0m"},
		True:     [2]string{"\033[38;5;219m", "\033[0m"},
		False:    [2]string{"\033[38;5;219m", "\033[0m"},
		Null:     [2]string{"\033[38;5;250m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;129m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;244m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// SunsetStyle mimics the colors of a sunset, using warm hues and deep purples.
	SunsetStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;214m", "\033[0m"},
		String:   [2]string{"\033[38;5;213m", "\033[0m"},
		Number:   [2]string{"\033[38;5;178m", "\033[0m"},
		True:     [2]string{"\033[38;5;229m", "\033[0m"},
		False:    [2]string{"\033[38;5;160m", "\033[0m"},
		Null:     [2]string{"\033[38;5;236m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;202m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;15m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// JungleStyle draws inspiration from a dense jungle with deep greens and browns.
	JungleStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;22m", "\033[0m"},
		String:   [2]string{"\033[38;5;28m", "\033[0m"},
		Number:   [2]string{"\033[38;5;130m", "\033[0m"},
		True:     [2]string{"\033[38;5;46m", "\033[0m"},
		False:    [2]string{"\033[38;5;166m", "\033[0m"},
		Null:     [2]string{"\033[38;5;143m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;94m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;23m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// MonochromeStyle uses different shades of black and white for a simple, high-contrast theme.
	MonochromeStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;235m", "\033[0m"},
		String:   [2]string{"\033[38;5;255m", "\033[0m"},
		Number:   [2]string{"\033[38;5;240m", "\033[0m"},
		True:     [2]string{"\033[38;5;255m", "\033[0m"},
		False:    [2]string{"\033[38;5;232m", "\033[0m"},
		Null:     [2]string{"\033[38;5;243m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;237m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;255m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// ForestStyle uses deep greens and browns to create a natural, earthy look.
	ForestStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;28m", "\033[0m"},
		String:   [2]string{"\033[38;5;35m", "\033[0m"},
		Number:   [2]string{"\033[38;5;130m", "\033[0m"},
		True:     [2]string{"\033[38;5;46m", "\033[0m"},
		False:    [2]string{"\033[38;5;88m", "\033[0m"},
		Null:     [2]string{"\033[38;5;102m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;136m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;24m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// IceStyle brings a cool, frosty aesthetic with blues and whites.
	IceStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;63m", "\033[0m"},
		String:   [2]string{"\033[38;5;159m", "\033[0m"},
		Number:   [2]string{"\033[38;5;81m", "\033[0m"},
		True:     [2]string{"\033[38;5;39m", "\033[0m"},
		False:    [2]string{"\033[38;5;35m", "\033[0m"},
		Null:     [2]string{"\033[38;5;15m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;44m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;66m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// RetroStyle brings back the vibrant colors from older computer systems and arcade games.
	RetroStyle = &encoding.Style{
		Key:      [2]string{"\033[1;38;5;208m", "\033[0m"},
		String:   [2]string{"\033[1;38;5;119m", "\033[0m"},
		Number:   [2]string{"\033[1;38;5;220m", "\033[0m"},
		True:     [2]string{"\033[1;38;5;51m", "\033[0m"},
		False:    [2]string{"\033[1;38;5;160m", "\033[0m"},
		Null:     [2]string{"\033[1;38;5;232m", "\033[0m"},
		Escape:   [2]string{"\033[1;38;5;161m", "\033[0m"},
		Brackets: [2]string{"\033[1;38;5;227m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// AutumnStyle uses rich oranges, reds, and browns, evoking the colors of fall.
	AutumnStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;130m", "\033[0m"},
		String:   [2]string{"\033[38;5;214m", "\033[0m"},
		Number:   [2]string{"\033[38;5;52m", "\033[0m"},
		True:     [2]string{"\033[38;5;166m", "\033[0m"},
		False:    [2]string{"\033[38;5;88m", "\033[0m"},
		Null:     [2]string{"\033[38;5;240m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;166m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;54m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// GothicStyle uses darker colors with a moody atmosphere, ideal for dark themes.
	GothicStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;235m", "\033[0m"},
		String:   [2]string{"\033[38;5;244m", "\033[0m"},
		Number:   [2]string{"\033[38;5;8m", "\033[0m"},
		True:     [2]string{"\033[38;5;68m", "\033[0m"},
		False:    [2]string{"\033[38;5;61m", "\033[0m"},
		Null:     [2]string{"\033[38;5;235m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;16m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;232m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// VaporWaveStyle embraces the retro aesthetics of vapor-wave, with bright neon and pastel colors.
	VaporWaveStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;219m", "\033[0m"},
		String:   [2]string{"\033[38;5;189m", "\033[0m"},
		Number:   [2]string{"\033[38;5;204m", "\033[0m"},
		True:     [2]string{"\033[38;5;207m", "\033[0m"},
		False:    [2]string{"\033[38;5;142m", "\033[0m"},
		Null:     [2]string{"\033[38;5;255m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;129m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;155m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// VampireStyle brings dark and sinister colors, with a touch of red for a spooky theme.
	VampireStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;88m", "\033[0m"},
		String:   [2]string{"\033[38;5;124m", "\033[0m"},
		Number:   [2]string{"\033[38;5;16m", "\033[0m"},
		True:     [2]string{"\033[38;5;160m", "\033[0m"},
		False:    [2]string{"\033[38;5;88m", "\033[0m"},
		Null:     [2]string{"\033[38;5;16m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;0m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;16m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// CarnivalStyle is inspired by a fun, bright carnival atmosphere, full of vivid, exciting colors.
	CarnivalStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;220m", "\033[0m"},
		String:   [2]string{"\033[38;5;204m", "\033[0m"},
		Number:   [2]string{"\033[38;5;202m", "\033[0m"},
		True:     [2]string{"\033[38;5;46m", "\033[0m"},
		False:    [2]string{"\033[38;5;160m", "\033[0m"},
		Null:     [2]string{"\033[38;5;214m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;213m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;33m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// SteampunkStyle has a vintage industrial look with brass and copper colors.
	SteampunkStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;136m", "\033[0m"},
		String:   [2]string{"\033[38;5;214m", "\033[0m"},
		Number:   [2]string{"\033[38;5;130m", "\033[0m"},
		True:     [2]string{"\033[38;5;184m", "\033[0m"},
		False:    [2]string{"\033[38;5;52m", "\033[0m"},
		Null:     [2]string{"\033[38;5;94m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;124m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;250m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// WoodlandStyle blends earthy tones with deep forest greens and browns.
	WoodlandStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;22m", "\033[0m"},
		String:   [2]string{"\033[38;5;36m", "\033[0m"},
		Number:   [2]string{"\033[38;5;130m", "\033[0m"},
		True:     [2]string{"\033[38;5;46m", "\033[0m"},
		False:    [2]string{"\033[38;5;160m", "\033[0m"},
		Null:     [2]string{"\033[38;5;143m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;94m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;23m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// CandyStyle is bright, with pastel hues that resemble candy colors.
	CandyStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;218m", "\033[0m"},
		String:   [2]string{"\033[38;5;226m", "\033[0m"},
		Number:   [2]string{"\033[38;5;222m", "\033[0m"},
		True:     [2]string{"\033[38;5;45m", "\033[0m"},
		False:    [2]string{"\033[38;5;51m", "\033[0m"},
		Null:     [2]string{"\033[38;5;255m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;196m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;226m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// TwilightStyle brings in dusky, cool tones reminiscent of dusk.
	TwilightStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;54m", "\033[0m"},
		String:   [2]string{"\033[38;5;123m", "\033[0m"},
		Number:   [2]string{"\033[38;5;39m", "\033[0m"},
		True:     [2]string{"\033[38;5;108m", "\033[0m"},
		False:    [2]string{"\033[38;5;166m", "\033[0m"},
		Null:     [2]string{"\033[38;5;242m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;124m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;239m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// EarthStyle reflects natural earthy colors with muted greens and browns.
	EarthStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;22m", "\033[0m"},
		String:   [2]string{"\033[38;5;130m", "\033[0m"},
		Number:   [2]string{"\033[38;5;52m", "\033[0m"},
		True:     [2]string{"\033[38;5;46m", "\033[0m"},
		False:    [2]string{"\033[38;5;208m", "\033[0m"},
		Null:     [2]string{"\033[38;5;130m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;94m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;24m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// ElectricStyle uses electric, bright neon colors for a futuristic vibe.
	ElectricStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;51m", "\033[0m"},
		String:   [2]string{"\033[38;5;87m", "\033[0m"},
		Number:   [2]string{"\033[38;5;93m", "\033[0m"},
		True:     [2]string{"\033[38;5;39m", "\033[0m"},
		False:    [2]string{"\033[38;5;160m", "\033[0m"},
		Null:     [2]string{"\033[38;5;255m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;196m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;227m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// WitchingHourStyle combines deep purples with dark greens for a magical look.
	WitchingHourStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;128m", "\033[0m"},
		String:   [2]string{"\033[38;5;88m", "\033[0m"},
		Number:   [2]string{"\033[38;5;91m", "\033[0m"},
		True:     [2]string{"\033[38;5;24m", "\033[0m"},
		False:    [2]string{"\033[38;5;231m", "\033[0m"},
		Null:     [2]string{"\033[38;5;234m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;29m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;102m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// MidnightStyle gives a mysterious and dark aesthetic, like a quiet midnight scene.
	MidnightStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;17m", "\033[0m"},
		String:   [2]string{"\033[38;5;73m", "\033[0m"},
		Number:   [2]string{"\033[38;5;135m", "\033[0m"},
		True:     [2]string{"\033[38;5;11m", "\033[0m"},
		False:    [2]string{"\033[38;5;124m", "\033[0m"},
		Null:     [2]string{"\033[38;5;244m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;17m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;233m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// RetroFutureStyle combines retro tones with a futuristic neon palette for a vintage-tech feel.
	RetroFutureStyle = &encoding.Style{
		Key:      [2]string{"\033[1;38;5;214m", "\033[0m"},
		String:   [2]string{"\033[1;38;5;189m", "\033[0m"},
		Number:   [2]string{"\033[1;38;5;220m", "\033[0m"},
		True:     [2]string{"\033[1;38;5;49m", "\033[0m"},
		False:    [2]string{"\033[1;38;5;231m", "\033[0m"},
		Null:     [2]string{"\033[1;38;5;250m", "\033[0m"},
		Escape:   [2]string{"\033[1;38;5;135m", "\033[0m"},
		Brackets: [2]string{"\033[1;38;5;254m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// ForestMistStyle invokes the serene and cool vibes of a misty forest.
	ForestMistStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;22m", "\033[0m"},
		String:   [2]string{"\033[38;5;118m", "\033[0m"},
		Number:   [2]string{"\033[38;5;140m", "\033[0m"},
		True:     [2]string{"\033[38;5;45m", "\033[0m"},
		False:    [2]string{"\033[38;5;56m", "\033[0m"},
		Null:     [2]string{"\033[38;5;242m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;235m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;249m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// PrismStyle offers a colorful, dazzling light prism effect for a modern, energetic look.
	PrismStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;39m", "\033[0m"},
		String:   [2]string{"\033[38;5;129m", "\033[0m"},
		Number:   [2]string{"\033[38;5;166m", "\033[0m"},
		True:     [2]string{"\033[38;5;51m", "\033[0m"},
		False:    [2]string{"\033[38;5;161m", "\033[0m"},
		Null:     [2]string{"\033[38;5;250m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;33m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;15m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// SpringStyle brings the fresh, light colors of spring to life.
	SpringStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;82m", "\033[0m"},
		String:   [2]string{"\033[38;5;214m", "\033[0m"},
		Number:   [2]string{"\033[38;5;190m", "\033[0m"},
		True:     [2]string{"\033[38;5;87m", "\033[0m"},
		False:    [2]string{"\033[38;5;161m", "\033[0m"},
		Null:     [2]string{"\033[38;5;249m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;33m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;255m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// DesertStyle evokes the warmth and serenity of a desert landscape.
	DesertStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;95m", "\033[0m"},
		String:   [2]string{"\033[38;5;130m", "\033[0m"},
		Number:   [2]string{"\033[38;5;137m", "\033[0m"},
		True:     [2]string{"\033[38;5;216m", "\033[0m"},
		False:    [2]string{"\033[38;5;197m", "\033[0m"},
		Null:     [2]string{"\033[38;5;248m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;167m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;230m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// SolarFlareStyle uses vibrant oranges and fiery reds, inspired by the intense heat of the sun.
	SolarFlareStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;214m", "\033[0m"},
		String:   [2]string{"\033[38;5;196m", "\033[0m"},
		Number:   [2]string{"\033[38;5;226m", "\033[0m"},
		True:     [2]string{"\033[38;5;220m", "\033[0m"},
		False:    [2]string{"\033[38;5;161m", "\033[0m"},
		Null:     [2]string{"\033[38;5;255m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;203m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;208m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// IceQueenStyle reflects a cool, frosty appearance with icy blues and silvers.
	IceQueenStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;81m", "\033[0m"},
		String:   [2]string{"\033[38;5;153m", "\033[0m"},
		Number:   [2]string{"\033[38;5;77m", "\033[0m"},
		True:     [2]string{"\033[38;5;45m", "\033[0m"},
		False:    [2]string{"\033[38;5;250m", "\033[0m"},
		Null:     [2]string{"\033[38;5;15m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;59m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;96m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// ForestGroveStyle brings earthy tones with a dense forest theme.
	ForestGroveStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;28m", "\033[0m"},
		String:   [2]string{"\033[38;5;48m", "\033[0m"},
		Number:   [2]string{"\033[38;5;130m", "\033[0m"},
		True:     [2]string{"\033[38;5;46m", "\033[0m"},
		False:    [2]string{"\033[38;5;238m", "\033[0m"},
		Null:     [2]string{"\033[38;5;144m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;94m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;36m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// AutumnLeavesStyle uses warm, fall-inspired hues like browns, reds, and golden yellows.
	AutumnLeavesStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;130m", "\033[0m"},
		String:   [2]string{"\033[38;5;172m", "\033[0m"},
		Number:   [2]string{"\033[38;5;52m", "\033[0m"},
		True:     [2]string{"\033[38;5;214m", "\033[0m"},
		False:    [2]string{"\033[38;5;136m", "\033[0m"},
		Null:     [2]string{"\033[38;5;240m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;217m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;95m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// VaporStyle uses pastel tones and calming shades of pink, purple, and blue.
	VaporStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;189m", "\033[0m"},
		String:   [2]string{"\033[38;5;153m", "\033[0m"},
		Number:   [2]string{"\033[38;5;81m", "\033[0m"},
		True:     [2]string{"\033[38;5;159m", "\033[0m"},
		False:    [2]string{"\033[38;5;129m", "\033[0m"},
		Null:     [2]string{"\033[38;5;255m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;144m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;113m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// SunsetBoulevardStyle mimics the stunning colors of a sunset, featuring warm oranges, pinks, and purples.
	SunsetBoulevardStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;214m", "\033[0m"},
		String:   [2]string{"\033[38;5;205m", "\033[0m"},
		Number:   [2]string{"\033[38;5;93m", "\033[0m"},
		True:     [2]string{"\033[38;5;226m", "\033[0m"},
		False:    [2]string{"\033[38;5;160m", "\033[0m"},
		Null:     [2]string{"\033[38;5;255m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;133m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;57m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// NeonCityStyle is bold and energetic, with electrifying neons of blue, pink, and green.
	NeonCityStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;51m", "\033[0m"},
		String:   [2]string{"\033[38;5;197m", "\033[0m"},
		Number:   [2]string{"\033[38;5;32m", "\033[0m"},
		True:     [2]string{"\033[38;5;82m", "\033[0m"},
		False:    [2]string{"\033[38;5;130m", "\033[0m"},
		Null:     [2]string{"\033[38;5;7m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;21m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;93m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// MoonlitNightStyle gives a serene and calm atmosphere with cool blues and soft silvers.
	MoonlitNightStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;18m", "\033[0m"},
		String:   [2]string{"\033[38;5;153m", "\033[0m"},
		Number:   [2]string{"\033[38;5;110m", "\033[0m"},
		True:     [2]string{"\033[38;5;48m", "\033[0m"},
		False:    [2]string{"\033[38;5;238m", "\033[0m"},
		Null:     [2]string{"\033[38;5;15m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;73m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;99m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// CandyShopStyle features bright, sugary tones of pinks, blues, and yellows for a fun and sweet theme.
	CandyShopStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;219m", "\033[0m"},
		String:   [2]string{"\033[38;5;186m", "\033[0m"},
		Number:   [2]string{"\033[38;5;81m", "\033[0m"},
		True:     [2]string{"\033[38;5;112m", "\033[0m"},
		False:    [2]string{"\033[38;5;196m", "\033[0m"},
		Null:     [2]string{"\033[38;5;255m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;222m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;174m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// UnderwaterStyle is inspired by the deep ocean, featuring calming blues and aquatic greens.
	UnderwaterStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;32m", "\033[0m"},
		String:   [2]string{"\033[38;5;51m", "\033[0m"},
		Number:   [2]string{"\033[38;5;39m", "\033[0m"},
		True:     [2]string{"\033[38;5;33m", "\033[0m"},
		False:    [2]string{"\033[38;5;236m", "\033[0m"},
		Null:     [2]string{"\033[38;5;15m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;73m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;45m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// OceanBreezeStyle reflects the calm and refreshing hues of the ocean.
	OceanBreezeStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;33m", "\033[0m"},
		String:   [2]string{"\033[38;5;75m", "\033[0m"},
		Number:   [2]string{"\033[38;5;51m", "\033[0m"},
		True:     [2]string{"\033[38;5;44m", "\033[0m"},
		False:    [2]string{"\033[38;5;61m", "\033[0m"},
		Null:     [2]string{"\033[38;5;250m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;32m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;244m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// CandyPopStyle brings a playful and sweet color palette, like a candy store.
	CandyPopStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;201m", "\033[0m"},
		String:   [2]string{"\033[38;5;207m", "\033[0m"},
		Number:   [2]string{"\033[38;5;220m", "\033[0m"},
		True:     [2]string{"\033[38;5;51m", "\033[0m"},
		False:    [2]string{"\033[38;5;160m", "\033[0m"},
		Null:     [2]string{"\033[38;5;248m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;33m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;255m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// NoirStyle gives a film-noir inspired look with dark, moody colors.
	NoirStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;16m", "\033[0m"},
		String:   [2]string{"\033[38;5;249m", "\033[0m"},
		Number:   [2]string{"\033[38;5;235m", "\033[0m"},
		True:     [2]string{"\033[38;5;231m", "\033[0m"},
		False:    [2]string{"\033[38;5;242m", "\033[0m"},
		Null:     [2]string{"\033[38;5;233m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;245m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;255m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// GalacticStyle evokes the mysterious vastness of outer space with deep, cosmic hues.
	GalacticStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;54m", "\033[0m"},
		String:   [2]string{"\033[38;5;92m", "\033[0m"},
		Number:   [2]string{"\033[38;5;129m", "\033[0m"},
		True:     [2]string{"\033[38;5;106m", "\033[0m"},
		False:    [2]string{"\033[38;5;166m", "\033[0m"},
		Null:     [2]string{"\033[38;5;233m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;25m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;247m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// VintagePastelStyle offers a retro aesthetic with soft, pastel tones for a gentle, nostalgic atmosphere.
	VintagePastelStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;213m", "\033[0m"},
		String:   [2]string{"\033[38;5;187m", "\033[0m"},
		Number:   [2]string{"\033[38;5;153m", "\033[0m"},
		True:     [2]string{"\033[38;5;79m", "\033[0m"},
		False:    [2]string{"\033[38;5;226m", "\033[0m"},
		Null:     [2]string{"\033[38;5;248m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;229m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;245m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// VintageFilmStyle is inspired by the golden era of cinema, featuring muted golds, sepias, and classic black.
	VintageFilmStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;220m", "\033[0m"},
		String:   [2]string{"\033[38;5;138m", "\033[0m"},
		Number:   [2]string{"\033[38;5;239m", "\033[0m"},
		True:     [2]string{"\033[38;5;220m", "\033[0m"},
		False:    [2]string{"\033[38;5;58m", "\033[0m"},
		Null:     [2]string{"\033[38;5;255m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;167m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;59m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// FireworksStyle captures the excitement of a night sky lit up by colorful fireworks, featuring bold reds, yellows, and purples.
	FireworksStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;196m", "\033[0m"},
		String:   [2]string{"\033[38;5;226m", "\033[0m"},
		Number:   [2]string{"\033[38;5;57m", "\033[0m"},
		True:     [2]string{"\033[38;5;196m", "\033[0m"},
		False:    [2]string{"\033[38;5;11m", "\033[0m"},
		Null:     [2]string{"\033[38;5;15m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;201m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;93m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// ArcticSnowStyle brings the cool, crisp whites and icy blues of the arctic tundra into the design.
	ArcticSnowStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;15m", "\033[0m"},
		String:   [2]string{"\033[38;5;45m", "\033[0m"},
		Number:   [2]string{"\033[38;5;33m", "\033[0m"},
		True:     [2]string{"\033[38;5;8m", "\033[0m"},
		False:    [2]string{"\033[38;5;7m", "\033[0m"},
		Null:     [2]string{"\033[38;5;255m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;153m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;75m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// ElectricVibeStyle takes on high-energy neon tones with a touch of electric brightness.
	ElectricVibeStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;27m", "\033[0m"},
		String:   [2]string{"\033[38;5;51m", "\033[0m"},
		Number:   [2]string{"\033[38;5;51m", "\033[0m"},
		True:     [2]string{"\033[38;5;15m", "\033[0m"},
		False:    [2]string{"\033[38;5;196m", "\033[0m"},
		Null:     [2]string{"\033[38;5;15m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;57m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;99m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// DesertSunsetStyle brings warm and deep hues inspired by the desert landscape at sunset.
	DesertSunsetStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;216m", "\033[0m"},
		String:   [2]string{"\033[38;5;215m", "\033[0m"},
		Number:   [2]string{"\033[38;5;166m", "\033[0m"},
		True:     [2]string{"\033[38;5;130m", "\033[0m"},
		False:    [2]string{"\033[38;5;52m", "\033[0m"},
		Null:     [2]string{"\033[38;5;15m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;58m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;94m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// PastelDreamStyle evokes a dreamy, soft pastel palette perfect for relaxed and whimsical visuals.
	PastelDreamStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;226m", "\033[0m"},
		String:   [2]string{"\033[38;5;193m", "\033[0m"},
		Number:   [2]string{"\033[38;5;153m", "\033[0m"},
		True:     [2]string{"\033[38;5;118m", "\033[0m"},
		False:    [2]string{"\033[38;5;189m", "\033[0m"},
		Null:     [2]string{"\033[38;5;248m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;41m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;245m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}

	// TropicalVibeStyle draws inspiration from lush tropical jungles, with bright and vibrant greens and yellows.
	TropicalVibeStyle = &encoding.Style{
		Key:      [2]string{"\033[38;5;46m", "\033[0m"},
		String:   [2]string{"\033[38;5;220m", "\033[0m"},
		Number:   [2]string{"\033[38;5;118m", "\033[0m"},
		True:     [2]string{"\033[38;5;105m", "\033[0m"},
		False:    [2]string{"\033[38;5;55m", "\033[0m"},
		Null:     [2]string{"\033[38;5;250m", "\033[0m"},
		Escape:   [2]string{"\033[38;5;39m", "\033[0m"},
		Brackets: [2]string{"\033[38;5;43m", "\033[0m"},
		Append:   func(dst []byte, c byte) []byte { return append(dst, c) },
	}
)
View Source
var (
	// DisableTransformers is a global flag that determines whether transformers should be applied
	// when processing JSON values. If set to true, transformers will not be applied to the JSON values.
	// If set to false, transformers will be applied as expected.
	DisableTransformers = false
)

Functions

func AddTransformer

func AddTransformer(name string, t Transformer)

AddTransformer registers a Transformer implementation under the given name in the global registry. Registering with an existing name overwrites the previous entry. This function is safe for concurrent use.

To register a plain function, wrap it with TransformerFunc:

fj.AddTransformer("upper", fj.TransformerFunc(func(json, arg string) string {
    return strings.ToUpper(json)
}))

To register a struct-based implementation, pass a value that satisfies the Transformer interface:

type myTransformer struct{ prefix string }

func (t *myTransformer) Apply(json, arg string) string {
    return t.prefix + json
}

fj.AddTransformer("prefixed", &myTransformer{prefix: "data:"})

func Avg

func Avg(json, path string) (float64, bool)

Avg returns the arithmetic mean of all numeric values produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The average value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,30]}`
v, ok := fj.Avg(json, "scores") // 20.0, true

func CoerceTo

func CoerceTo(ctx Context, into any) error

CoerceTo converts the JSON value held in ctx into the Go variable pointed to by into, using the conv.Infer conversion engine. into must be a non-nil pointer to a supported type (bool, int*, uint*, float*, string, time.Time, slices, maps, or any struct with JSON-compatible fields).

This function provides a bridge between fj's Context values and Go's type system, enabling ergonomic extraction of typed values without manual type-assertion chains.

Parameters:

  • ctx: The Context whose value should be coerced.
  • into: A non-nil pointer to the target variable.

Returns:

  • An error if the context has no value, or if the conversion fails.

Example:

ctx := fj.Get(json, "user.age")
var age int
if err := fj.CoerceTo(ctx, &age); err == nil {
    fmt.Println(age) // 30
}

ctx = fj.Get(json, "user.active")
var active bool
_ = fj.CoerceTo(ctx, &active) // active == true

func CollectFloat64

func CollectFloat64(json, path string) []float64

CollectFloat64 evaluates path against json and returns a slice of float64 values for every element that can be coerced to a number by conv.Float64. This includes both JSON Number values and JSON strings that represent valid numbers (e.g., "42", "3.14").

Non-numeric elements for which conv.Float64 returns an error are silently skipped.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path resolving to an array or a single scalar.

Returns:

  • A slice of float64 values. Returns an empty (non-nil) slice when no elements can be coerced to float64.

Example:

json := `{"data":["10","20.5",30,null,"skip"]}`
vals := fj.CollectFloat64(json, "data")
// vals == []float64{10, 20.5, 30}

func Contains

func Contains(json, path, target string) bool

Contains reports whether the result of querying json at path contains the given target substring (case-sensitive). If the path does not exist, Contains returns false.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.
  • target: The substring to look for within the string representation of the value found at path.

Returns:

  • true if the value exists and its string representation contains target.

Example:

json := `{"msg":"hello world"}`
fj.Contains(json, "msg", "world") // true
fj.Contains(json, "msg", "xyz")   // false

func ContainsMatch

func ContainsMatch(json, path, pattern string) bool

ContainsMatch reports whether the value at path in json matches the given wildcard pattern. If the path does not exist, ContainsMatch returns false.

The pattern follows the same syntax as match.Match:

  • '*' matches any sequence of characters.
  • '?' matches exactly one character.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.
  • pattern: A wildcard pattern applied to the string representation of the value.

Returns:

  • true if the value exists and its string representation matches pattern.

Example:

json := `{"email":"alice@example.com"}`
fj.ContainsMatch(json, "email", "*@example.com") // true
fj.ContainsMatch(json, "email", "*@other.com")   // false

func Count

func Count(json, path string) int

Count returns the number of elements returned by evaluating path against json. For a path that produces a JSON array the count equals the array length. For a path that produces a single scalar value, Count returns 1. For a missing or null result, Count returns 0.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The count of matching elements (≥ 0).

Example:

json := `{"tags":["go","json","fast"]}`
fj.Count(json, "tags")   // 3
fj.Count(json, "tags.0") // 1
fj.Count(json, "missing")// 0

func FindPath

func FindPath(json, value string) string

FindPath returns the first dot-notation path in the JSON document at which the given scalar value can be found (case-sensitive, exact string match against Context.String()). Object keys and array indices are joined by ".".

FindPath only searches leaf (scalar) values. If no leaf matches, an empty string is returned.

Parameters:

  • json: A well-formed JSON string.
  • value: The scalar string value to locate.

Returns:

  • The dot-notation path of the first matching leaf, or "" when not found.

Example:

json := `{"user":{"name":"Alice","age":30}}`
fj.FindPath(json, "Alice") // "user.name"

func FindPathMatch

func FindPathMatch(json, valuePattern string) string

FindPathMatch returns the first dot-notation path in the JSON document at which a scalar value matches the given wildcard pattern. Object keys and array indices are joined with ".".

Only leaf (scalar) values are tested. If no leaf matches, an empty string is returned.

Parameters:

  • json: A well-formed JSON string.
  • valuePattern: A wildcard pattern applied to the string representation of each scalar leaf.

Returns:

  • The dot-notation path of the first matching leaf, or "" when not found.

Example:

json := `{"users":[{"name":"Alice"},{"name":"Bob"}]}`
fj.FindPathMatch(json, "Ali*") // "users.0.name"

func FindPaths

func FindPaths(json, value string) []string

FindPaths returns the dot-notation paths for every leaf in the JSON document whose string representation exactly equals value (case-sensitive).

Parameters:

  • json: A well-formed JSON string.
  • value: The scalar string value to locate.

Returns:

  • All matching paths in depth-first order. Returns an empty slice when there are no matches.

Example:

json := `{"a":"x","b":{"c":"x","d":"y"}}`
fj.FindPaths(json, "x") // ["a", "b.c"]

func FindPathsMatch

func FindPathsMatch(json, valuePattern string) []string

FindPathsMatch returns the dot-notation paths for every scalar leaf in the JSON document whose string representation matches the given wildcard pattern.

Parameters:

  • json: A well-formed JSON string.
  • valuePattern: A wildcard pattern applied to the string representation of each scalar leaf.

Returns:

  • All matching paths in depth-first order. Returns an empty slice when there are no matches.

Example:

json := `{"a":"Alice","b":{"c":"Albany","d":"Bob"}}`
fj.FindPathsMatch(json, "Al*") // ["a", "b.c"]

func Foreach

func Foreach(json string, iterator func(line Context) bool)

Foreach iterates through each line of JSON data in the JSON Lines format (http://jsonlines.org/), and applies a provided iterator function to each line. This is useful for processing large JSON data sets where each line is a separate JSON object, allowing for efficient parsing and handling of each object.

Parameters:

  • `json`: A string containing JSON Lines formatted data, where each line is a separate JSON object.
  • `iterator`: A callback function that is called for each line. It receives a `Context` representing the parsed JSON object for the current line. The iterator function should return `true` to continue processing the next line, or `false` to stop the iteration.

Example Usage:

json := `{"name": "Alice"}\n{"name": "Bob"}`
iterator := func(line Context) bool {
    fmt.Println(line)
    return true
}
Foreach(json, iterator)
// Output:
// {"name": "Alice"}
// {"name": "Bob"}

Notes:

  • This function assumes the input `json` is formatted as JSON Lines, where each line is a valid JSON object.
  • The function stops processing as soon as the `iterator` function returns `false` for a line.
  • The function handles each line independently, meaning it processes one JSON object at a time and provides it to the iterator, which can be used to process or filter lines.

Returns:

  • This function does not return a value. It processes the JSON data line-by-line and applies the iterator to each.

func GroupBy

func GroupBy(json, path, keyField string) map[string][]Context

GroupBy evaluates path against json, treats the result as an array of objects, and groups the elements by the string value of the specified keyField using conv.String for key normalization.

Elements that do not contain keyField, or for which the key cannot be converted to a string, are placed under the empty-string group "".

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path resolving to an array of objects.
  • keyField: The object field whose value is used as the group key.

Returns:

  • A map from group-key string to a slice of Context values in that group. Returns an empty map when path does not exist or the result is not an array.

Example:

json := `{"books":[
    {"title":"Clean Code","genre":"tech"},
    {"title":"Dune","genre":"fiction"},
    {"title":"The Go Book","genre":"tech"}
]}`
groups := fj.GroupBy(json, "books", "genre")
// groups["tech"]    → 2 elements
// groups["fiction"] → 1 element

func IsTransformerRegistered

func IsTransformerRegistered(name string) bool

IsTransformerRegistered reports whether a transformer with the given name has been registered in the global registry.

This function is safe for concurrent use by multiple goroutines.

func IsValidJSON

func IsValidJSON(json string) bool

IsValidJSON checks whether the provided string contains valid JSON data. It attempts to parse the JSON and returns a boolean indicating if the JSON is well-formed.

Parameters:

  • `json`: A string representing the JSON data that needs to be validated.

Returns:

  • A boolean value (`true` or `false`):
  • `true`: The provided JSON string is valid and well-formed.
  • `false`: The provided JSON string is invalid or malformed.

Notes:

  • This function utilizes the `fromStr2Bytes` function to efficiently convert the input string into a byte slice without allocating new memory. It then passes the byte slice to the `verifyJSON` function to check if the string conforms to valid JSON syntax.
  • If the input JSON is invalid, the function will return `false`, indicating that the JSON cannot be parsed or is improperly structured.
  • The function does not perform deep validation of the content of the JSON, but merely checks if the string is syntactically correct according to JSON rules.

Example Usage:

json := `{"name": {"first": "Alice", "last": "Johnson"}, "age": 30}`
if !IsValidJSON(json) {
    fmt.Println("Invalid JSON")
} else {
    fmt.Println("IsValidJSON JSON")
}

// Output: "IsValidJSON JSON"

func IsValidJSONBytes

func IsValidJSONBytes(json []byte) bool

IsValidJSONBytes checks whether the provided byte slice contains valid JSON data. It attempts to parse the JSON and returns a boolean indicating if the JSON is well-formed.

Parameters:

  • `json`: A byte slice (`[]byte`) representing the JSON data that needs to be validated.

Returns:

  • A boolean value (`true` or `false`):
  • `true`: The provided JSON byte slice is valid and well-formed.
  • `false`: The provided JSON byte slice is invalid or malformed.

Notes:

  • This function works directly with a byte slice (`[]byte`) rather than a string, making it more efficient when dealing with raw byte data that represents JSON. It avoids the need to convert between strings and byte slices, which can improve performance and memory usage when working with large or binary JSON data.
  • The function utilizes the `verifyJSON` function to check if the byte slice conforms to valid JSON syntax.
  • If the input byte slice represents invalid JSON, the function will return `false`, indicating that the JSON cannot be parsed or is improperly structured.
  • The function does not perform deep validation of the content of the JSON, but only checks whether the byte slice adheres to the syntax rules defined for JSON data structures.

Example Usage:

jsonBytes := []byte(`{"name": {"first": "Alice", "last": "Johnson"}, "age": 30}`)
if !IsValidJSONBytes(jsonBytes) {
    fmt.Println("Invalid JSON")
} else {
    fmt.Println("Valid JSON")
}

// Output: "Valid JSON"

func Max

func Max(json, path string) (float64, bool)

Max returns the maximum numeric value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The maximum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.Max(json, "scores") // 30.0, true

func MaxFloat32

func MaxFloat32(json, path string) (float32, bool)

MaxFloat32 returns the maximum numeric value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The maximum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.MaxFloat32(json, "scores") // 30.0, true

func MaxInt

func MaxInt(json, path string) (int, bool)

MaxInt returns the maximum integer value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The maximum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.MaxInt(json, "scores") // 30, true

func MaxInt8

func MaxInt8(json, path string) (int8, bool)

MaxInt8 returns the maximum integer value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The maximum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.MaxInt8(json, "scores") // 30, true

func MaxInt16

func MaxInt16(json, path string) (int16, bool)

MaxInt16 returns the maximum integer value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The maximum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.MaxInt16(json, "scores") // 30, true

func MaxInt32

func MaxInt32(json, path string) (int32, bool)

MaxInt32 returns the maximum integer value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The maximum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.MaxInt32(json, "scores") // 30, true

func MaxInt64

func MaxInt64(json, path string) (int64, bool)

MaxInt64 returns the maximum integer value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The maximum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.MaxInt64(json, "scores") // 30, true

func MaxUint

func MaxUint(json, path string) (uint, bool)

MaxUint returns the maximum unsigned integer value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The maximum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.MaxUint(json, "scores") // 30, true

func MaxUint8

func MaxUint8(json, path string) (uint8, bool)

MaxUint8 returns the maximum unsigned integer value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The maximum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.MaxUint8(json, "scores") // 30, true

func Min

func Min(json, path string) (float64, bool)

Min returns the minimum numeric value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The minimum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.Min(json, "scores") // 5.0, true

func MinFloat32

func MinFloat32(json, path string) (float32, bool)

MinFloat32 returns the minimum numeric value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The minimum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.MinFloat32(json, "scores") // 5.0, true

func MinInt

func MinInt(json, path string) (int, bool)

MinInt returns the minimum integer value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The minimum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.MinInt(json, "scores") // 5, true

func MinInt8

func MinInt8(json, path string) (int8, bool)

MinInt8 returns the minimum integer value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The minimum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.MinInt8(json, "scores") // 5, true

func MinInt16

func MinInt16(json, path string) (int16, bool)

MinInt16 returns the minimum integer value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The minimum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.MinInt16(json, "scores") // 5, true

func MinInt32

func MinInt32(json, path string) (int32, bool)

MinInt32 returns the minimum integer value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The minimum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.MinInt32(json, "scores") // 5, true

func MinInt64

func MinInt64(json, path string) (int64, bool)

MinInt64 returns the minimum integer value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The minimum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.MinInt64(json, "scores") // 5, true

func MinUint

func MinUint(json, path string) (uint, bool)

MinUint returns the minimum unsigned integer value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The minimum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.MinUint(json, "scores") // 5, true

func MinUint8

func MinUint8(json, path string) (uint8, bool)

MinUint8 returns the minimum unsigned integer value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The minimum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.MinUint8(json, "scores") // 5, true

func MinUint16

func MinUint16(json, path string) (uint16, bool)

MinUint16 returns the minimum unsigned integer value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The minimum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.MinUint16(json, "scores") // 5, true

func MinUint32

func MinUint32(json, path string) (uint32, bool)

MinUint32 returns the minimum unsigned integer value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The minimum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.MinUint32(json, "scores") // 5, true

func MinUint64

func MinUint64(json, path string) (uint64, bool)

MinUint64 returns the minimum unsigned integer value among all results produced by evaluating path against json. Non-numeric results are silently ignored.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • The minimum value and true when at least one number is found.
  • 0 and false when no numeric values are found.

Example:

json := `{"scores":[10,20,5,30]}`
v, ok := fj.MinUint64(json, "scores") // 5, true

func Sum

func Sum(json, path string) float64

Sum returns the sum of all numeric values produced by evaluating path against json. Non-numeric results are silently ignored. Returns 0 when no numeric values are found.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path. The path may resolve to a JSON array of numbers (e.g. "scores") or a single number (e.g. "scores.0").

Returns:

  • The sum as float64.

Example:

json := `{"scores":[10,20,30]}`
fj.Sum(json, "scores") // 60.0

func SumFloat32

func SumFloat32(json, path string) float32

SumFloat32 returns the sum of all numeric values produced by evaluating path against json. Non-numeric results are silently ignored. Returns 0 when no numeric values are found.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path. The path may resolve to a JSON array of numbers (e.g. "scores") or a single number (e.g. "scores.0").

Returns:

  • The sum as float32.

Example:

json := `{"scores":[10,20,30]}`
fj.SumFloat32(json, "scores") // 60.0

func SumInt

func SumInt(json, path string) int

SumInt returns the sum of all numeric values produced by evaluating path against json. Non-numeric results are silently ignored. Returns 0 when no numeric values are found.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path. The path may resolve to a JSON array of numbers (e.g. "scores") or a single number (e.g. "scores.0").

Returns:

  • The sum as int.

Example:

json := `{"scores":[10,20,30]}`
fj.SumInt(json, "scores") // 60

func SumInt8

func SumInt8(json, path string) int8

SumInt8 returns the sum of all numeric values produced by evaluating path against json. Non-numeric results are silently ignored. Returns 0 when no numeric values are found.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path. The path may resolve to a JSON array of numbers (e.g. "scores") or a single number (e.g. "scores.0").

Returns:

  • The sum as int8.

Example:

json := `{"scores":[10,20,30]}`
fj.SumInt8(json, "scores") // 60

func SumInt16

func SumInt16(json, path string) int16

SumInt16 returns the sum of all numeric values produced by evaluating path against json. Non-numeric results are silently ignored. Returns 0 when no numeric values are found.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path. The path may resolve to a JSON array of numbers (e.g. "scores") or a single number (e.g. "scores.0").

Returns:

  • The sum as int16.

Example:

json := `{"scores":[10,20,30]}`
fj.SumInt16(json, "scores") // 60

func SumInt32

func SumInt32(json, path string) int32

SumInt32 returns the sum of all numeric values produced by evaluating path against json. Non-numeric results are silently ignored. Returns 0 when no numeric values are found.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path. The path may resolve to a JSON array of numbers (e.g. "scores") or a single number (e.g. "scores.0").

Returns:

  • The sum as int32.

Example:

json := `{"scores":[10,20,30]}`
fj.SumInt32(json, "scores") // 60

func SumInt64

func SumInt64(json, path string) int64

SumInt64 returns the sum of all numeric values produced by evaluating path against json. Non-numeric results are silently ignored. Returns 0 when no numeric values are found.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path. The path may resolve to a JSON array of numbers (e.g. "scores") or a single number (e.g. "scores.0").

Returns:

  • The sum as int64.

Example:

json := `{"scores":[10,20,30]}`
fj.SumInt64(json, "scores") // 60

func SumUint

func SumUint(json, path string) uint

SumUint returns the sum of all numeric values produced by evaluating path against json. Non-numeric results are silently ignored. Returns 0 when no numeric values are found.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path. The path may resolve to a JSON array of numbers (e.g. "scores") or a single number (e.g. "scores.0").

Returns:

  • The sum as uint.

Example:

json := `{"scores":[10,20,30]}`
fj.SumUint(json, "scores") // 60

func SumUint8

func SumUint8(json, path string) uint8

SumUint8 returns the sum of all numeric values produced by evaluating path against json. Non-numeric results are silently ignored. Returns 0 when no numeric values are found.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path. The path may resolve to a JSON array of numbers (e.g. "scores") or a single number (e.g. "scores.0").

Returns:

  • The sum as uint8.

Example:

json := `{"scores":[10,20,30]}`
fj.SumUint8(json, "scores") // 60

func SumUint16

func SumUint16(json, path string) uint16

SumUint16 returns the sum of all numeric values produced by evaluating path against json. Non-numeric results are silently ignored. Returns 0 when no numeric values are found.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path. The path may resolve to a JSON array of numbers (e.g. "scores") or a single number (e.g. "scores.0").

Returns:

  • The sum as uint16.

Example:

json := `{"scores":[10,20,30]}`
fj.SumUint16(json, "scores") // 60

func SumUint32

func SumUint32(json, path string) uint32

SumUint32 returns the sum of all numeric values produced by evaluating path against json. Non-numeric results are silently ignored. Returns 0 when no numeric values are found.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path. The path may resolve to a JSON array of numbers (e.g. "scores") or a single number (e.g. "scores.0").

Returns:

  • The sum as uint32.

Example:

json := `{"scores":[10,20,30]}`
fj.SumUint32(json, "scores") // 60

func SumUint64

func SumUint64(json, path string) uint64

SumUint64 returns the sum of all numeric values produced by evaluating path against json. Non-numeric results are silently ignored. Returns 0 when no numeric values are found.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path. The path may resolve to a JSON array of numbers (e.g. "scores") or a single number (e.g. "scores.0").

Returns:

  • The sum as uint64.

Example:

json := `{"scores":[10,20,30]}`
fj.SumUint64(json, "scores") // 60

func UnsafeBytes

func UnsafeBytes(s string) []byte

UnsafeBytes converts a string into a byte slice without allocating new memory for the data. This function uses unsafe operations to directly reinterpret the string's underlying data structure as a byte slice. This allows efficient access to the string's content as a mutable byte slice, but it also comes with risks.

Parameters:

  • `s`: The input string that needs to be converted to a byte slice.

Returns:

  • A byte slice (`[]byte`) that shares the same underlying data as the input string.

Notes:

  • This function leverages Go's `unsafe` package to bypass the usual safety mechanisms of the Go runtime. It does this by manipulating memory layouts using `unsafe.Pointer`.
  • The resulting byte slice must be treated with care. Modifying the byte slice can lead to undefined behavior since strings in Go are immutable by design.
  • Any operation that depends on the immutability of the original string should avoid using this function.

Safety Considerations:

  • Since this function operates on unsafe pointers, it is not portable across different Go versions or architectures.
  • Direct modifications to the returned byte slice will violate Go's immutability guarantees for strings and may corrupt program state.

Example Usage:

s := "immutable string"
b := UnsafeBytes(s) // Efficiently converts the string to []byte
// WARNING: Modifying 'b' here can lead to undefined behavior.

Types

type Context

type Context struct {
	// contains filtered or unexported fields
}

Context represents a JSON value returned from the Get() function. It stores information about a specific JSON element, including its type, raw string data, string representation, numeric value, index in the original JSON, and the indexes of elements that match a path containing a '#'.

func Distinct

func Distinct(json, path string) []Context

Distinct evaluates path against json and returns a deduplicated slice of values using the string representation of each element as the equality key. Order is preserved (first occurrence wins).

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.

Returns:

  • A slice of unique Context values. Returns an empty slice when the path does not exist.

Example:

json := `{"tags":["go","json","go","fast","json"]}`
results := fj.Distinct(json, "tags")
// len(results) == 3: "go", "json", "fast"

func Filter

func Filter(json, path string, fn func(Context) bool) []Context

Filter evaluates path against json, treats the result as an array, and returns only those elements for which fn returns true.

If path resolves to a non-array value (including a single scalar), that single value is treated as a one-element collection. If the path does not exist, an empty slice is returned.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.
  • fn: A predicate applied to each element. Returning true keeps the element.

Returns:

  • A slice of matching Context values.

Example:

json := `{"items":[1,2,3,4,5]}`
results := fj.Filter(json, "items", func(ctx fj.Context) bool {
    return ctx.Float64() > 2
})
// results holds Context values for 3, 4, 5

func First

func First(json, path string, fn func(Context) bool) Context

First evaluates path against json and returns the first element for which fn returns true. If no element matches, a zero-value Context is returned (use Context.Exists() to check).

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path.
  • fn: A predicate applied to each element.

Returns:

  • The first matching Context, or a zero-value Context when not found.

Example:

json := `{"items":[1,2,3,4,5]}`
ctx := fj.First(json, "items", func(c fj.Context) bool {
    return c.Float64() > 3
})
ctx.Float64() // 4

func Get

func Get(json, path string) Context

Get searches for a specified path within the provided JSON string and returns the corresponding value as a Context. The path is provided in dot notation, where each segment represents a key or index. The function supports wildcards (`*` and `?`), array indexing, and special characters like '#' to access array lengths or child paths. The function will return the first matching result it finds along the specified path.

Path Syntax: - Dot notation: "name.last" or "age" for direct key lookups. - Wildcards: "*" matches any key, "?" matches a single character. - Array indexing: "children.0" accesses the first item in the "children" array. - The '#' character returns the number of elements in an array (e.g., "children.#" returns the array length). - The dot (`.`) and wildcard characters (`*`, `?`) can be escaped with a backslash (`\`).

Example Usage:

json := `{
  "user": {"firstName": "Alice", "lastName": "Johnson"},
  "age": 29,
  "siblings": ["Ben", "Clara", "David"],
  "friends": [
    {"firstName": "Tom", "lastName": "Smith"},
    {"firstName": "Sophia", "lastName": "Davis"}
  ],
  "address": {"city": "New York", "zipCode": "10001"}
}`

// Examples of Get function with paths:
Get(json, "user.lastName")        // Returns: "Johnson"
Get(json, "age")                  // Returns: 29
Get(json, "siblings.#")           // Returns: 3 (number of siblings)
Get(json, "siblings.1")           // Returns: "Clara" (second sibling)
Get(json, "friends.#.firstName")  // Returns: ["Tom", "Sophia"]
Get(json, "address.zipCode")      // Returns: "10001"

Details:

  • The function does not validate JSON format but expects well-formed input. Invalid JSON may result in unexpected behavior.
  • transformers (e.g., `@` for adjusting paths) and special sub-selectors (e.g., `[` and `{`) are supported and processed in the path before extracting values.
  • For complex structures, the function analyzes the provided path, handles nested arrays or objects, and returns a Context containing the value found at the specified location.

Parameters:

  • `json`: A string containing the JSON data to search through.
  • `path`: A string representing the path to the desired value, using dot notation or other special characters as described.

Returns:

  • `Context`: A Context object containing the value found at the specified path, including information such as the type (`kind`), the raw JSON string (`raw`), and the parsed value if available (e.g., `str` for strings).

Notes:

  • If the path is not found, the returned Context will reflect this with an empty or null value.

func GetBytes

func GetBytes(json []byte, path string) Context

GetBytes searches the provided JSON byte slice for the specified path and returns a `Context` representing the extracted data. This method is preferred over `Get(string(data), path)` when working with JSON data in byte slice format, as it directly operates on the byte slice, minimizing memory allocations and unnecessary copies.

Parameters:

  • `json`: A byte slice containing the JSON data to process.
  • `path`: A string representing the path in the JSON data to extract.

Returns:

  • A `Context` struct containing the processed JSON data. The `Context` struct includes both the raw unprocessed JSON string and the specific extracted string based on the given path.

Notes:

  • This function internally calls the `getBytes` function, which uses unsafe pointer operations to minimize allocations and efficiently handle string slice headers.
  • The function avoids unnecessary memory allocations by directly processing the byte slice and utilizing memory safety features to manage substring extraction when the `str` part is a substring of the `raw` part of the JSON data.

Example:

jsonBytes := []byte(`{"key": "value", "nested": {"innerKey": "innerValue"}}`)
path := "nested.innerKey"
context := GetBytes(jsonBytes, path)
fmt.Println("Unprocessed:", context.raw) // Output: `{"key": "value", "nested": {"innerKey": "innerValue"}}`
fmt.Println("Strings:", context.str)         // Output: `"innerValue"`

func GetBytesMulti

func GetBytesMulti(json []byte, path ...string) []Context

GetBytesMulti searches json for multiple paths in the provided JSON byte slice. The return value is a slice of `Context` objects, where the number of items will be equal to the number of input paths. Each `Context` represents the value extracted for the corresponding path. This method operates directly on the byte slice, which is preferred when working with JSON data in byte format to minimize memory allocations.

Parameters:

  • `json`: A byte slice containing the JSON data to search through.
  • `path`: A variadic list of paths to search for within the JSON data.

Returns:

  • A slice of `Context` objects, one for each path provided in the `path` parameter.

Notes:

  • The function will return a `Context` for each path, and the order of the `Context` objects in the result will match the order of the paths provided.

Example:

jsonBytes := []byte(`{"user": {"firstName": "Alice", "lastName": "Johnson"}, "age": 29}`)
paths := []string{"user.lastName", "age"}
results := GetBytesMulti(jsonBytes, paths...)
// The result will contain Contexts for each path: ["Johnson", 29]

func GetMulti

func GetMulti(json string, path ...string) []Context

GetMulti searches json for multiple paths. The return value is a slice of `Context` objects, where the number of items will be equal to the number of input paths. Each `Context` represents the value extracted for the corresponding path.

Parameters:

  • `json`: A string containing the JSON data to search through.
  • `path`: A variadic list of paths to search for within the JSON data.

Returns:

  • A slice of `Context` objects, one for each path provided in the `path` parameter.

Notes:

  • The function will return a `Context` for each path, and the order of the `Context` objects in the result will match the order of the paths provided.

Example:

json := `{
  "user": {"firstName": "Alice", "lastName": "Johnson"},
  "age": 29,
  "siblings": ["Ben", "Clara", "David"],
  "friends": [
    {"firstName": "Tom", "lastName": "Smith"},
    {"firstName": "Sophia", "lastName": "Davis"}
  ]
}`
paths := []string{"user.lastName", "age", "siblings.#", "friends.#.firstName"}
results := GetMulti(json, paths...)
// The result will contain Contexts for each path: ["Johnson", 29, 3, ["Tom", "Sophia"]]

func Parse

func Parse(json string) Context

Parse parses a JSON string and returns a Context representing the parsed value.

This function processes the input JSON string and attempts to determine the type of the value it represents. It handles objects, arrays, numbers, strings, booleans, and null values. The function does not validate whether the JSON is well-formed, and instead returns a Context object that represents the first valid JSON element found in the string. Invalid JSON may result in unexpected behavior, so for input from unpredictable sources, consider using the `Valid` function first.

Parameters:

  • `json`: A string containing the JSON data to be parsed. This function expects well-formed JSON and does not perform comprehensive validation.

Returns:

  • A `Context` that represents the parsed JSON element. The `Context` contains details about the type, value, and position of the JSON element, including raw and unprocessed string data.

Notes:

  • The function attempts to determine the type of the JSON element by inspecting the first character in the string. It supports the following types: Object (`{`), Array (`[`), Number, String (`"`), Boolean (`true` / `false`), and Null (`null`).
  • The function sets the `raw` field of the `Context` to the raw JSON string for further processing, and sets the `kind` field to represent the type of the value (e.g., `String`, `Number`, `True`, `False`, `JSON`, `Null`).

Example Usage:

json := "{\"name\": \"John\", \"age\": 30}"
ctx := Parse(json)
fmt.Println(ctx.kind) // Output: JSON (if the input starts with '{')

json := "12345"
ctx := Parse(json)
fmt.Println(ctx.kind) // Output: Number (if the input is a numeric value)

json := "\"Hello, World!\""
ctx := Parse(json)
fmt.Println(ctx.kind) // Output: String (if the input is a string)

Returns:

  • `Context`: The parsed result, which may represent an object, array, string, number, boolean, or null.

func ParseBytes

func ParseBytes(json []byte) Context

ParseBytes parses a JSON byte slice and returns a Context representing the parsed value.

This function is a wrapper around the `Parse` function, designed specifically for handling JSON data in the form of a byte slice. It converts the byte slice into a string and then calls `Parse` to process the JSON data. If you're working with raw JSON data as bytes, using this method is preferred over manually converting the bytes to a string and passing it to `Parse`.

Parameters:

  • `json`: A byte slice containing the JSON data to be parsed.

Returns:

  • A `Context` representing the parsed JSON element, similar to the behavior of `Parse`. The `Context` contains information about the type, value, and position of the JSON element, including the raw and unprocessed string data.

Example Usage:

json := []byte("{\"name\": \"Alice\", \"age\": 25}")
ctx := ParseBytes(json)
fmt.Println(ctx.kind) // Output: JSON (if the input is an object)

Returns:

  • `Context`: The parsed result, representing the parsed JSON element, such as an object, array, string, number, boolean, or null.

func ParseJSONFile

func ParseJSONFile(filepath string) Context

ParseJSONFile reads a JSON string from a file specified by the filepath and parses it into a Context.

This function opens the specified file, reads its contents using the `ParseReader` function, and returns a Context representing the parsed JSON element. If any error occurs during file reading or JSON parsing, the returned Context will include the error details.

Parameters:

  • filepath: The path to the JSON file to be read and parsed.

Returns:

  • A `Context` representing the parsed JSON element. If an error occurs during the file reading or parsing process, the `err` field of the returned Context will be set with the error, and the other fields will remain empty.

Example Usage:

ctx := ParseJSONFile("example.json")
if ctx.err != nil {
    log.Fatalf("Failed to parse JSON: %v", ctx.err)
}
fmt.Println(ctx.String())

Notes:

  • This function is useful for reading and parsing JSON directly from files.
  • The file is opened and closed automatically within this function, and any errors encountered are captured in the returned `Context`.

func ParseReader

func ParseReader(in io.Reader) Context

ParseReader reads a JSON string from an `io.Reader` and parses it into a Context.

This function combines the reading and parsing operations. It first reads the JSON string using the `BufioRead` function, then processes the string using the `Parse` function to extract details about the first valid JSON element. If reading the JSON fails, the returned Context contains the encountered error.

Parameters:

  • in: An `io.Reader` from which the JSON string will be read. This could be a file, network connection, or any other source that implements the `io.Reader` interface.

Returns:

  • A `Context` representing the parsed JSON element. If an error occurs during the reading process, the `err` field of the `Context` is set with the error, and the other fields remain empty.

Example Usage:

// Reading JSON from a file
file, err := os.Open("example.json")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

ctx := ParseReader(file)
if ctx.err != nil {
    log.Fatalf("Failed to parse JSON: %v", ctx.err)
}
fmt.Println(ctx.kind)

// Reading JSON from standard input
fmt.Println("Enter JSON:")
ctx = ParseReader(os.Stdin)
if ctx.err != nil {
    log.Fatalf("Failed to parse JSON: %v", ctx.err)
}
fmt.Println(ctx.kind)

Notes:

  • This function is particularly useful when working with JSON data from streams or large files.
  • The `Parse` function is responsible for the actual parsing, while this function ensures the JSON string is read correctly before parsing begins.
  • If the input JSON is malformed or invalid, the returned Context from `Parse` will reflect the issue as an empty Context or an error in the `err` field.

func Pluck

func Pluck(json, path string, fields ...string) []Context

Pluck evaluates path against json (expecting a JSON array of objects), and for each element builds a new JSON object containing only the specified fields. Fields absent from an element are omitted from its output object.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path resolving to an array of objects.
  • fields: The field names to extract from each object.

Returns:

  • A slice of Context values, one per element in the source array, each wrapping the projected JSON object. Returns an empty slice when path does not exist or no fields are provided.

Example:

json := `{"users":[
    {"id":1,"name":"Alice","email":"a@example.com"},
    {"id":2,"name":"Bob","email":"b@example.com"}
]}`
results := fj.Pluck(json, "users", "id", "name")
// results[0].String() == `{"id":1,"name":"Alice"}`
// results[1].String() == `{"id":2,"name":"Bob"}`
func Search(json, keyword string) []Context

Search performs a full-tree scan of the JSON document and returns all leaf values whose string representation contains the given keyword (case-sensitive substring match). Both string values and non-string scalar values (numbers, booleans, null) are compared against the keyword.

The traversal is depth-first; array elements and object values are each visited recursively. Compound values (objects and arrays) are never returned as matches — only scalar leaves are considered.

Parameters:

  • json: A well-formed JSON string to scan.
  • keyword: The substring to search for. An empty keyword matches every leaf.

Returns:

  • A slice of Context values whose string representation contains keyword. Returns an empty (non-nil) slice when there are no matches.

Example:

json := `{"users":[{"name":"Alice"},{"name":"Bob"},{"name":"Charlie"}]}`
results := fj.Search(json, "Ali")
// results[0].String() == "Alice"

func SearchByKey

func SearchByKey(json string, keys ...string) []Context

SearchByKey performs a full-tree scan of the JSON document and returns all Context values that are stored under any of the given key names. The search is recursive — it descends into nested objects and arrays at every depth level.

Key matching is exact and case-sensitive. Multiple key names may be supplied; any value whose immediate parent key matches at least one of them is included.

Parameters:

  • json: A well-formed JSON string to scan.
  • keys: One or more object key names to look up. If no keys are provided the function returns an empty slice.

Returns:

  • A slice of Context values stored under the given keys, in depth-first order.

Example:

json := `{"a":{"title":"Go"},"b":{"title":"Rust"},"c":{"other":"x"}}`
results := fj.SearchByKey(json, "title")
// len(results) == 2, results[0].String() == "Go"

func SearchByKeyPattern

func SearchByKeyPattern(json, keyPattern string) []Context

SearchByKeyPattern performs a full-tree scan of the JSON document and returns all Context values whose immediate parent object key matches the given wildcard pattern.

Key matching uses match.Match, supporting '*' (any sequence) and '?' (one character) wildcards. The scan is recursive, descending into nested objects and arrays.

Parameters:

  • json: A well-formed JSON string to scan.
  • keyPattern: A wildcard pattern applied to object key names.

Returns:

  • A slice of Context values stored under matching keys, in depth-first order.

Example:

json := `{"author":"Donovan","authority":"admin","title":"Go"}`
results := fj.SearchByKeyPattern(json, "auth*")
// len(results) == 2: "Donovan" (author) and "admin" (authority)

func SearchMatch

func SearchMatch(json, pattern string) []Context

SearchMatch performs a full-tree scan of the JSON document and returns all scalar leaf values whose string representation matches the given wildcard pattern.

The pattern follows the same syntax as match.Match:

  • '*' matches any sequence of characters (including empty).
  • '?' matches exactly one character.
  • Any other character is matched literally.

Both string and non-string scalar values (numbers, booleans, null) are tested against the pattern using their string representation.

Parameters:

  • json: A well-formed JSON string to scan.
  • pattern: A wildcard pattern. An empty pattern matches only an empty string.

Returns:

  • A slice of Context values whose string representation matches pattern. Returns an empty (non-nil) slice when there are no matches.

Example:

json := `{"users":[{"name":"Alice"},{"name":"Bob"},{"name":"Albany"}]}`
results := fj.SearchMatch(json, "Al*")
// len(results) == 2: "Alice", "Albany"

func SortBy

func SortBy(json, path, keyField string, ascending bool) []Context

SortBy evaluates path against json, treats the result as an array, and returns a new slice sorted by the value of keyField on each element. Sorting uses conv.Float64 for numeric fields and falls back to the string representation for non-numeric or missing fields.

When keyField is empty, elements are sorted by their own top-level string representation, which is useful for arrays of scalars.

Parameters:

  • json: A well-formed JSON string.
  • path: A fj dot-notation path resolving to an array.
  • keyField: The object field to sort by. Pass "" to sort scalar arrays directly.
  • ascending: If true, sort in ascending order; if false, descending.

Returns:

  • A new sorted slice of Context values. Returns an empty slice when path does not exist.

Example:

json := `{"items":[{"n":3},{"n":1},{"n":2}]}`
sorted := fj.SortBy(json, "items", "n", true)
// sorted[0].Get("n").Int64() == 1
// sorted[1].Get("n").Int64() == 2
// sorted[2].Get("n").Int64() == 3

func (Context) Array

func (ctx Context) Array() []Context

Array returns an array of `Context` values derived from the current `Context`.

Behavior:

  • If the current `Context` represents a `Null` value, it returns an empty array.
  • If the current `Context` is not a JSON array, it returns an array containing itself as a single element.
  • If the current `Context` is a JSON array, it parses and returns the array's elements.

Returns:

  • []Context: A slice of `Context` values representing the array elements.

Example Usage:

ctx := Context{kind: Null}
arr := ctx.Array()
// arr: []

ctx = Context{kind: JSON, raw: "[1, 2, 3]"}
arr = ctx.Array()
// arr: [Context, Context, Context]

Notes:

  • This function uses `parseJSONElements` internally to extract array elements.
  • If the JSON is malformed or does not represent an array, the behavior may vary.

func (Context) Bool

func (ctx Context) Bool() bool

Bool converts the Context value into a boolean representation. The conversion depends on the JSON type of the Context:

  • For `True` type: Returns `true`.
  • For `String` type: Attempts to parse the string as a boolean (case-insensitive). If parsing fails, defaults to `false`.
  • For `Number` type: Returns `true` if the numeric value is non-zero, otherwise `false`.
  • For all other types: Returns `false`.

Returns:

  • bool: A boolean representation of the Context value.

func (Context) Cause

func (ctx Context) Cause() string

Cause returns the error message if there is an error in the Context.

If the Context has an error (i.e., `err` is not `nil`), this function returns the error message as a string. If there is no error, it returns an empty string.

Example Usage:

ctx := Context{err: fmt.Errorf("parsing error")}
fmt.Println(ctx.Cause()) // Output: "parsing error"

ctx = Context{}
fmt.Println(ctx.Cause()) // Output: ""

Returns:

  • string: The error message if there is an error, or an empty string if there is no error.

func (Context) Duration

func (ctx Context) Duration() time.Duration

Duration converts the Context value into a time.Duration representation. The conversion depends on the JSON type of the Context:

  • For `Number` type: Returns the numeric value as a time.Duration.
  • For `String` type: Attempts to parse the string as a duration using `time.ParseDuration`. If parsing fails, defaults to `time.Duration(0)`.
  • For all other types: Returns `time.Duration(0)`.

Returns:

  • time.Duration: A time.Duration representation of the Context value. Defaults to `time.Duration(0)` if parsing fails.

func (Context) Exists

func (ctx Context) Exists() bool

Exists returns true if the value exists (i.e., it is not Null and contains data).

Example Usage:

if fj.Get(json, "user.name").Exists() {
  println("value exists")
}

Returns:

  • bool: Returns true if the value is not null and contains non-empty data, otherwise returns false.

func (Context) Float32

func (ctx Context) Float32() float32

Float32 converts the Context value into a floating-point representation (Float32). This function provides a similar conversion mechanism as Float64 but with Float32 precision. The conversion depends on the JSON type of the Context:

  • For `True` type: Returns 1 as a Float32 value.
  • For `String` type: Attempts to parse the string as a floating-point number (Float32 precision). If the parsing fails, it defaults to 0.
  • For `Number` type: Returns the numeric value as a Float32, assuming the Context contains a numeric value in its `num` field.

Returns:

  • Float32: A floating-point representation of the Context value.

Example Usage:

ctx := Context{kind: String, strings: "123.45"}
result := ctx.Float32()
// result: 123.45 (parsed as Float32)

ctx = Context{kind: True}
result = ctx.Float32()
// result: 1 (True is represented as 1.0)

ctx = Context{kind: Number, numeric: 678.9}
result = ctx.Float32()
// result: 678.9 (as Float32)

Details:

  • For the `True` type, the function always returns 1.0, representing the boolean `true` value.

  • For the `String` type, it uses `strconv.ParseFloat` with 32-bit precision to convert the string into a Float32. If parsing fails (e.g., if the string is not a valid numeric representation), the function returns 0 as a fallback.

  • For the `Number` type, the `num` field, assumed to hold a float64 value, is converted to a Float32 for the return value.

Notes:

  • The function gracefully handles invalid string inputs for the `String` type by returning 0, ensuring no runtime panic occurs due to a parsing error.

  • Precision may be lost when converting from float64 (`num` field) to Float32.

func (Context) Float64

func (ctx Context) Float64() float64

Float64 converts the Context value into a floating-point representation (float64). The conversion depends on the JSON type of the Context:

  • For `True` type: Returns 1.
  • For `String` type: Attempts to parse the string as a floating-point number. Defaults to 0 on failure.
  • For `Number` type: Returns the numeric value as a float64.

Returns:

  • float64: A floating-point representation of the Context value.

func (Context) Foreach

func (ctx Context) Foreach(iterator func(key, value Context) bool)

Foreach iterates through the values of a JSON object or array, applying the provided iterator function.

If the `Context` represents a non existent value (Null or invalid JSON), no iteration occurs. For JSON objects, the iterator receives both the key and value of each item. For JSON arrays, the iterator receives only the value of each item. If the `Context` is not an array or object, the iterator is called once with the whole value.

Example Usage:

ctx.Foreach(func(key, value Context) bool {
  if key.str != "" {
    fmt.Printf("Key: %s, Value: %v\n", key.str, value)
  } else {
    fmt.Printf("Value: %v\n", value)
  }
  return true // Continue iteration
})

Parameters:

  • iterator: A function that receives a `key` (for objects) and `value` (for both objects and arrays). The function should return `true` to continue iteration or `false` to stop.

Notes:

  • If the result is a JSON object, the iterator receives key-value pairs.
  • If the result is a JSON array, the iterator receives only the values.
  • If the result is not an object or array, the iterator is invoked once with the value.

Returns:

  • None. The iteration continues until all items are processed or the iterator returns `false`.

func (Context) Get

func (ctx Context) Get(path string) Context

Get searches for a specified path within a JSON structure and returns the corresponding result.

This function allows you to search for a specific path in the JSON structure and retrieve the corresponding value as a `Context`. The path is represented as a string and can be used to navigate nested arrays or objects.

The `path` parameter specifies the JSON path to search for, and the function will attempt to retrieve the value associated with that path. The result is returned as a `Context`, which contains information about the matched JSON value, including its type, string representation, numeric value, and index in the original JSON.

Parameters:

  • path: A string representing the path in the JSON structure to search for. The path may include array indices and object keys separated by dots or brackets (e.g., "user.name", "items[0].price").

Returns:

  • Context: A `Context` instance containing the result of the search. The `Context` may represent various types of JSON values (e.g., string, number, object, array). If no match is found, the `Context` will be empty.

Example Usage:

ctx := Context{kind: JSON, raw: "{\"user\": {\"name\": \"John\"}, \"items\": [1, 2, 3]}"}
result := ctx.Get("user.name")
// result.str will contain "John", representing the value found at the "user.name" path.

Notes:

  • The function uses the `Get` function (presumably another function) to process the `raw` JSON string and search for the specified path.
  • The function adjusts the indices of the results (if any) to account for the original position of the `Context` in the JSON string.

func (Context) GetMulti

func (ctx Context) GetMulti(path ...string) []Context

GetMulti searches for multiple paths within a JSON structure and returns a slice of results.

This function allows you to search for multiple paths in the JSON structure, each represented as a string. It returns a slice of `Context` instances for each of the specified paths. The paths can be used to navigate nested arrays or objects.

The `path` parameters specify the JSON paths to search for, and the function will attempt to retrieve the values associated with those paths. Each result is returned as a `Context` containing information about the matched JSON value, including its type, string representation, numeric value, and index in the original JSON.

Parameters:

  • path: One or more strings representing the paths in the JSON structure to search for. The paths may include array indices and object keys separated by dots or brackets (e.g., "user.name", "items[0].price").

Returns:

  • []Context: A slice of `Context` instances, each containing the result of searching for one of the paths. Each `Context` may represent various types of JSON values (e.g., string, number, object, array). If no match is found for a path, the corresponding `Context` will be empty.

Example Usage:

ctx := Context{kind: JSON, raw: "{\"user\": {\"name\": \"John\"}, \"items\": [1, 2, 3]}"}
results := ctx.GetMulti("user.name", "items[1]")
// results[0].str will contain "John" for the "user.name" path,
// results[1].num will contain 2 for the "items[1]" path.

Notes:

  • This function uses the `GetMulti` function (presumably another function) to process the `raw` JSON string and search for each of the specified paths.
  • Each result is returned as a separate `Context` for each path, allowing for multiple values to be retrieved at once from the JSON structure.

func (Context) Index

func (ctx Context) Index() int

Index returns the index of the unprocessed JSON value in the original JSON string. This can be used to track the position of the value in the source data. If the index is unknown, it defaults to 0.

Returns:

  • int: The position of the value in the original JSON string.

func (Context) Indexes

func (ctx Context) Indexes() []int

Indexes returns a slice of indices for elements matching a path containing the '#' character. This is useful for handling path queries that involve multiple matches.

Returns:

  • []int: A slice of indices for matching elements.

func (Context) Int

func (ctx Context) Int() int

Int converts the Context value into an integer representation (int). The conversion depends on the JSON type of the Context:

  • For `True` type: Returns 1.
  • For `String` type: Attempts to parse the string into an integer. Defaults to 0 on failure.
  • For `Number` type:
  • Directly converts the numeric value to an integer if it's within the safe range.
  • Parses the unprocessed string for integer values as a fallback.
  • Defaults to converting the float64 numeric value to an int.

Returns:

  • int: An integer representation of the Context value.

func (Context) Int8

func (ctx Context) Int8() int8

Int8 converts the Context value into an 8-bit integer representation (int8). The conversion depends on the JSON type of the Context:

  • For `True` type: Returns 1.
  • For `String` type: Attempts to parse the string into an 8-bit integer. Defaults to 0 on failure.
  • For `Number` type:
  • Directly converts the numeric value to an 8-bit integer if it's within the safe range.
  • Parses the unprocessed string for 8-bit integer values as a fallback.
  • Defaults to converting the float64 numeric value to an int8.

Returns:

  • int8: An 8-bit integer representation of the Context value.

func (Context) Int16

func (ctx Context) Int16() int16

Int16 converts the Context value into a 16-bit integer representation (int16). The conversion depends on the JSON type of the Context:

  • For `True` type: Returns 1.
  • For `String` type: Attempts to parse the string into a 16-bit integer. Defaults to 0 on failure.
  • For `Number` type:
  • Directly converts the numeric value to a 16-bit integer if it's within the safe range.
  • Parses the unprocessed string for 16-bit integer values as a fallback.
  • Defaults to converting the float64 numeric value to an int16.

Returns:

  • int16: A 16-bit integer representation of the Context value.

func (Context) Int32

func (ctx Context) Int32() int32

Int32 converts the Context value into a 32-bit integer representation (int32). The conversion depends on the JSON type of the Context:

  • For `True` type: Returns 1.
  • For `String` type: Attempts to parse the string into a 32-bit integer. Defaults to 0 on failure.
  • For `Number` type:
  • Directly converts the numeric value to a 32-bit integer if it's within the safe range.
  • Parses the unprocessed string for 32-bit integer values as a fallback.
  • Defaults to converting the float64 numeric value to an int32.

Returns:

  • int32: A 32-bit integer representation of the Context value.

func (Context) Int64

func (ctx Context) Int64() int64

Int64 converts the Context value into an integer representation (int64). The conversion depends on the JSON type of the Context:

  • For `True` type: Returns 1.
  • For `String` type: Attempts to parse the string into an integer. Defaults to 0 on failure.
  • For `Number` type:
  • Directly converts the numeric value to an integer if it's safe.
  • Parses the unprocessed string for integer values as a fallback.
  • Defaults to converting the float64 numeric value to an int64.

Returns:

  • int64: An integer representation of the Context value.

func (Context) IsArray

func (ctx Context) IsArray() bool

IsArray checks if the current `Context` represents a JSON array.

A value is considered a JSON array if:

  • The `kind` is `JSON`.
  • The `raw` string starts with the `[` character.

Returns:

  • bool: Returns `true` if the `Context` is a JSON array; otherwise, `false`.

Example Usage:

ctx := Context{kind: JSON, raw: "[1, 2, 3]"}
isArr := ctx.IsArray()
// isArr: true

ctx = Context{kind: JSON, raw: "{"key": "value"}"}
isArr = ctx.IsArray()
// isArr: false

func (Context) IsBool

func (ctx Context) IsBool() bool

IsBool checks if the current `Context` represents a JSON boolean value.

A value is considered a JSON boolean if:

  • The `kind` is `True` or `False`.

Returns:

  • bool: Returns `true` if the `Context` is a JSON boolean; otherwise, `false`.

Example Usage:

ctx := Context{kind: True}
isBool := ctx.IsBool()
// isBool: true

ctx = Context{kind: String, strings: "true"}
isBool = ctx.IsBool()
// isBool: false

func (Context) IsError

func (ctx Context) IsError() bool

IsError checks if there is an error associated with the Context.

This function checks whether the `err` field of the Context is set. If there is an error (i.e., `err` is not `nil`), the function returns `true`; otherwise, it returns `false`.

Example Usage:

ctx := Context{err: fmt.Errorf("invalid JSON")}
fmt.Println(ctx.IsError()) // Output: true

ctx = Context{}
fmt.Println(ctx.IsError()) // Output: false

Returns:

  • bool: `true` if the Context has an error, `false` otherwise.

func (Context) IsObject

func (ctx Context) IsObject() bool

IsObject checks if the current `Context` represents a JSON object.

A value is considered a JSON object if:

  • The `kind` is `JSON`.
  • The `raw` string starts with the `{` character.

Returns:

  • bool: Returns `true` if the `Context` is a JSON object; otherwise, `false`.

Example Usage:

ctx := Context{kind: JSON, raw: "{"key": "value"}"}
isObj := ctx.IsObject()
// isObj: true

ctx = Context{kind: JSON, raw: "[1, 2, 3]"}
isObj = ctx.IsObject()
// isObj: false

func (Context) Kind

func (ctx Context) Kind() Type

Kind returns the JSON type of the Context. It provides the specific type of the JSON value, such as String, Number, Object, etc.

Returns:

  • Type: The type of the JSON value represented by the Context.

func (Context) Less

func (ctx Context) Less(token Context, caseSensitive bool) bool

Less compares two Context values (tokens) and returns true if the first token is considered less than the second one. It performs comparisons based on the type of the tokens and their respective values. The comparison order follows: Null < False < Number < String < True < JSON. This function also supports case-insensitive comparisons for String type tokens based on the caseSensitive parameter.

Parameters:

  • token: The other Context token to compare with the current one (t).
  • caseSensitive: A boolean flag that indicates whether the comparison for String type tokens should be case-sensitive.
  • If true, the comparison is case-sensitive (i.e., "a" < "b" but "A" < "b").
  • If false, the comparison is case-insensitive (i.e., "a" == "A").

Returns:

  • true: If the current token (t) is considered less than the provided token.
  • false: If the current token (t) is not considered less than the provided token.

The function first compares the `kind` of both tokens, which represents their JSON types. If both tokens have the same kind, it proceeds to compare based on their specific types: - For String types, it compares the strings based on the case-sensitive flag. - For Number types, it compares the numeric values directly. - For other types, it compares the unprocessed JSON values as raw strings (this could be useful for types like Null, Boolean, etc.).

Example usage:

context1 := Context{kind: String, strings: "apple"}
context2 := Context{kind: String, strings: "banana"}
result := context1.Less(context2, true) // This would return true because "apple" < "banana" and case-sensitive comparison is used.

func (Context) Map

func (ctx Context) Map() map[string]Context

Map returns a map of values extracted from a JSON object.

The function assumes that the `Context` represents a JSON object. It parses the JSON object and returns a map where the keys are strings, and the values are `Context` elements representing the corresponding JSON values.

If the `Context` does not represent a valid JSON object, the function will return an empty map.

Parameters:

  • ctx: The `Context` instance that holds the raw JSON string. The function checks if the context represents a JSON object and processes it accordingly.

Returns:

  • map[string]Context: A map where the keys are strings (representing the keys in the JSON object), and the values are `Context` instances representing the corresponding JSON values. If the context does not represent a valid JSON object, a nil is returned.

Example Usage:

ctx := Context{kind: JSON, raw: "{\"key1\": \"value1\", \"key2\": 42}"}
result := ctx.Map()
// result.OpMap contains the parsed key-value pairs: {"key1": "value1", "key2": 42}

Notes:

  • The function calls `parseJSONElements` with the expected JSON object indicator ('{') to parse the JSON.
  • If the `Context` is not a valid JSON object, it returns an empty map, which can be used to safely handle errors.

func (Context) Number

func (ctx Context) Number() float64

Number returns the numeric value of the Context, if applicable. This is relevant when the Context represents a JSON number.

Returns:

  • float64: The numeric value of the Context. If the Context does not represent a number, the value may be undefined.

func (Context) Path

func (ctx Context) Path(json string) string

Path returns the original fj path for a Result where the Result came from a simple query path that returns a single value. For example, if the `Get` function was called with a query path like:

fj.Get(json, "employees.#(first=Admin)")

This function will return the original path that corresponds to the single value in the result, formatted as a JSON path.

The returned value will be in the form of a JSON string:

"employees.0"

The param 'json' must be the original JSON used when calling Get.

Returns:

  • A string representing the original path for the single value in the result.
  • If the paths cannot be determined (e.g., due to the result being from a multi-path, transformer, or a nested query), an empty string will be returned.

Notes:

  • The `Path` function operates by tracing the position of the result within the original JSON string and reconstructing the query path based on this position.
  • The function checks the surrounding JSON context (such as whether the result is within an array or object) and extracts the relevant path information.
  • The path components are identified by traversing the string from the result's index and extracting the array or object keys that lead to the specific value.

Example Usage:

json := `{
  "employees": [
    {"id": 1, "name": {"first": "John", "last": "Doe"}, "department": "HR"},
    {"id": 2, "name": {"first": "Jane", "last": "Smith"}, "department": "Engineering"},
    {"id": 3, "name": {"first": "Admin", "last": "Land"}, "department": "Marketing"},
    {"id": 4, "name": {"first": "Emily", "last": "Jones"}, "department": "Engineering"}
  ],
  "companies": [
    {"name": "TechCorp", "employees": [1, 2]},
    {"name": "BizGroup", "employees": [3, 4]}
  ]
}`

// Get the employee's last name who works in the Engineering department
result := fj.Get(json, "employees.#(department=Engineering).name.last")
path := result.Path(json)

// Output: "employees.1.name.last"

// Explanation:
// The `Path` function returns the path to the "last" name of the second
// employee in the "employees" array who works in the "Engineering" department.
// The path "employees.1.name.last" corresponds to the "Jane Smith" employee,
// and the query specifically looks at the "last" name of that employee.

func (Context) Paths

func (ctx Context) Paths(json string) []string

Paths returns the original fj paths for a Result where the Result came from a simple query path that returns an array. For example, if the `Get` function was called with a query path like:

fj.Get(json, "friends.#.first")

This function will return the paths for each element in the resulting array, formatted as a JSON array. The returned paths are the original query paths for each item in the array, reflecting the specific positions of the elements in the original JSON structure.

The returned value will be in the form of a JSON array, such as:

["friends.0.first", "friends.1.first", "friends.2.first"]

Parameters:

  • `json`: A string representing the original JSON used in the query. This is required for resolving the specific paths corresponding to each element in the resulting array.

Returns:

  • A slice of strings (`[]string`), each containing the original path for an element in the result array.
  • If the result was a simple query that returns an array, each string will be a path to an individual element in the array.
  • If the paths cannot be determined (e.g., due to the result being from a multi-path, transformer, or a nested query), an empty slice will be returned.

Notes:

  • The `Paths` function relies on the `idxs` field in the `Context` object. If the `idxs` field is `nil`, the function will return `nil`.
  • The function iterates over each element in the result (which is expected to be an array) and appends the corresponding path to the `paths` slice.
  • If the paths cannot be determined (e.g., due to the result coming from a multi-path or more complex query), an empty slice will be returned.
  • This function is useful for extracting the specific query paths for elements within a larger result array, providing a way to inspect or manipulate the paths of individual items.

Example Usage:

json := `{
  "friends": [
    {"first": "Tom", "last": "Smith"},
    {"first": "Sophia", "last": "Davis"},
    {"first": "James", "last": "Miller"}
  ]
}`

result := fj.Get(json, "friends.#.first")
paths := result.Paths(json)

// Output: ["friends.0.first", "friends.1.first", "friends.2.first"]

func (Context) Raw

func (ctx Context) Raw() string

Raw returns the raw, unprocessed JSON string for the Context. This can be useful for inspecting the original data without any parsing or transformations.

Returns:

  • string: The unprocessed JSON string.

func (Context) String

func (ctx Context) String() string

String returns a string representation of the Context value. The output depends on the JSON type of the Context:

  • For `False` type: Returns "false".
  • For `True` type: Returns "true".
  • For `Number` type: Returns the numeric value as a string. If the numeric value was calculated, it formats the float value. Otherwise, it preserves the original unprocessed string if valid.
  • For `String` type: Returns the string value.
  • For `JSON` type: Returns the raw unprocessed JSON string.
  • For other types: Returns an empty string.

Returns:

  • string: A string representation of the Context value.

func (Context) StringColored

func (ctx Context) StringColored() string

StringColored returns a colored string representation of the Context value. It applies the default style defined in `defaultStyle` to the string representation of the Context value.

Details:

  • The function first retrieves the plain string representation using `ctx.String()`.
  • If the string is empty (determined by `isEmpty`), it returns an empty string.
  • Otherwise, it applies the coloring rules from `defaultStyle` using the `encoding.Color` function.

Returns:

  • string: A colored string representation of the Context value if not empty. Returns an empty string for empty or invalid Context values.

Example Usage:

ctx := Context{kind: True}
fmt.Println(ctx.StringColored()) // Output: "\033[1;35mtrue\033[0m" (colored)

Notes:

  • Requires the `encoding` library for styling and the `isEmpty` utility function to check for empty strings.

func (Context) Time

func (ctx Context) Time() time.Time

Time converts the Context value into a time.Time representation. The conversion interprets the Context value as a string in RFC3339 format. If parsing fails, the zero time (0001-01-01 00:00:00 UTC) is returned.

Returns:

  • time.Time: A time.Time representation of the Context value. Defaults to the zero time if parsing fails.

func (Context) Uint

func (ctx Context) Uint() uint

Uint converts the Context value into an unsigned integer representation (uint). The conversion depends on the JSON type of the Context:

  • For `True` type: Returns 1.
  • For `String` type: Attempts to parse the string into an unsigned integer. Defaults to 0 on failure.
  • For `Number` type:
  • Directly converts the numeric value to a uint if it's safe and non-negative.
  • Parses the unprocessed string for unsigned integer values as a fallback.
  • Defaults to converting the float64 numeric value to a uint.

Returns:

  • uint: An unsigned integer representation of the Context value.

func (Context) Uint8

func (ctx Context) Uint8() uint8

Uint8 converts the Context value into an 8-bit unsigned integer representation (uint8). The conversion depends on the JSON type of the Context:

  • For `True` type: Returns 1.
  • For `String` type: Attempts to parse the string into an 8-bit unsigned integer. Defaults to 0 on failure.
  • For `Number` type:
  • Directly converts the numeric value to a uint8 if it's safe and non-negative.
  • Parses the unprocessed string for 8-bit unsigned integer values as a fallback.
  • Defaults to converting the float64 numeric value to a uint8.

Returns:

  • uint8: An 8-bit unsigned integer representation of the Context value.

func (Context) Uint16

func (ctx Context) Uint16() uint16

Uint16 converts the Context value into a 16-bit unsigned integer representation (uint16). The conversion depends on the JSON type of the Context:

  • For `True` type: Returns 1.
  • For `String` type: Attempts to parse the string into a 16-bit unsigned integer. Defaults to 0 on failure.
  • For `Number` type:
  • Directly converts the numeric value to a uint16 if it's safe and non-negative.
  • Parses the unprocessed string for 16-bit unsigned integer values as a fallback.
  • Defaults to converting the float64 numeric value to a uint16.

Returns:

  • uint16: A 16-bit unsigned integer representation of the Context value.

func (Context) Uint32

func (ctx Context) Uint32() uint32

Uint32 converts the Context value into a 32-bit unsigned integer representation (uint32). The conversion depends on the JSON type of the Context:

  • For `True` type: Returns 1.
  • For `String` type: Attempts to parse the string into a 32-bit unsigned integer. Defaults to 0 on failure.
  • For `Number` type:
  • Directly converts the numeric value to a uint32 if it's safe and non-negative.
  • Parses the unprocessed string for 32-bit unsigned integer values as a fallback.
  • Defaults to converting the float64 numeric value to a uint32.

Returns:

  • uint32: A 32-bit unsigned integer representation of the Context value.

func (Context) Uint64

func (ctx Context) Uint64() uint64

Uint64 converts the Context value into an unsigned integer representation (uint64). The conversion depends on the JSON type of the Context:

  • For `True` type: Returns 1.
  • For `String` type: Attempts to parse the string into an unsigned integer. Defaults to 0 on failure.
  • For `Number` type:
  • Directly converts the numeric value to a uint64 if it's safe and non-negative.
  • Parses the unprocessed string for unsigned integer values as a fallback.
  • Defaults to converting the float64 numeric value to a uint64.

Returns:

  • uint64: An unsigned integer representation of the Context value.

func (Context) Value

func (ctx Context) Value() any

Value returns the corresponding Go type for the JSON value represented by the Context.

The function returns one of the following types based on the JSON value:

  • bool for JSON booleans (True or False)
  • float64 for JSON numbers
  • string for JSON string literals
  • nil for JSON null
  • map[string]interface{} for JSON objects
  • []interface{} for JSON arrays

Example Usage:

value := ctx.Value()
switch v := value.(type) {
  case bool:
    fmt.Println("Boolean:", v)
  case float64:
    fmt.Println("Number:", v)
  case string:
    fmt.Println("String:", v)
  case nil:
    fmt.Println("Null value")
  case map[string]interface{}:
    fmt.Println("Object:", v)
  case []interface{}:
    fmt.Println("Array:", v)
}

Returns:

  • interface{}: Returns the corresponding Go type for the JSON value, or nil if the type is not recognized.

func (Context) WithStringColored

func (ctx Context) WithStringColored(style *encoding.Style) string

WithStringColored applies a customizable colored styling to the string representation of the Context value.

This function enhances the default coloring functionality by allowing the caller to specify a custom style for highlighting the Context value. If no custom style is provided, the default styling rules (`defaultStyle`) are used.

Parameters:

  • style (*encoding.Style): A pointer to a Style structure that defines custom styling rules for JSON elements. If `style` is nil, the `defaultStyle` is applied.

Details:

  • Retrieves the plain string representation of the Context value using `ctx.String()`.
  • Checks if the string is empty using the `isEmpty` utility function. If empty, it returns an empty string immediately.
  • If a custom style is provided, it applies the given style to the string representation using the `encoding.Color` function. Otherwise, it applies the default style.

Returns:

  • string: A styled string representation of the Context value based on the provided or default style.

Example Usage:

customStyle := &encoding.Style{
    Key:      [2]string{"\033[1;36m", "\033[0m"},
    String:   [2]string{"\033[1;33m", "\033[0m"},
    // Additional styling rules...
}

ctx := Context{kind: True}
fmt.Println(ctx.WithStringColored(customStyle)) // Output: "\033[1;35mtrue\033[0m" (custom colored)

Notes:

  • The function uses the `encoding.Color` utility to apply the color rules defined in the style.
  • Requires the `isEmpty` utility function to check for empty strings.

func (Context) WithTime

func (ctx Context) WithTime(format string) (time.Time, error)

WithTime parses the Context value into a time.Time representation using a custom format. This function allows for greater flexibility by enabling parsing with user-defined date and time formats, rather than relying on the fixed RFC3339 format used in `Time()`.

Parameters:

  • format: A string representing the desired format to parse the Context value. This format must conform to the layouts supported by the `time.Parse` function.

Returns:

  • time.Time: The parsed time.Time representation of the Context value if parsing succeeds.
  • error: An error value if the parsing fails (e.g., due to an invalid format or mismatched value).

Example Usage:

ctx := Context{kind: String, strings: "12-25-2023"}
t, err := ctx.WithTime("01-02-2006")
if err != nil {
    fmt.Println("Error parsing time:", err)
} else {
    fmt.Println("Parsed time:", t)
}
// Output: Parsed time: 2023-12-25 00:00:00 +0000 UTC

Details:

  • The function relies on the `time.Parse` function to convert the string value from the Context into a time.Time representation.

  • The format parameter determines how the Context value should be interpreted. It must match the layout of the Context's string value. If the value cannot be parsed according to the given format, the function returns an error.

  • Unlike `Time()`, which defaults to the zero time on failure, this function explicitly returns an error to indicate parsing issues.

Notes:

  • The function assumes that `ctx.String()` returns a string representation of the Context value. If the Context does not contain a valid string, the parsing will fail.

  • This function is ideal for cases where date and time formats vary or when the RFC3339 standard is not suitable.

  • To handle parsing failures gracefully, always check the returned error before using the resulting `time.Time` value.

type Transformer

type Transformer interface {
	Apply(json, arg string) string
}

Transformer is the interface implemented by all transformer types. A transformer receives the current JSON string and an optional argument string, and returns a transformed JSON string. Transformers are applied via the @ syntax in fj path expressions (e.g. "name.@uppercase").

Implement this interface to provide custom, stateful, or composable transformer logic that goes beyond plain function closures. For simple, stateless cases use the TransformerFunc adapter, which satisfies this interface automatically.

Example (struct-based):

type prefixTransformer struct{ prefix string }

func (t *prefixTransformer) Apply(json, arg string) string {
    return t.prefix + json
}

fj.AddTransformer("prefix", &prefixTransformer{prefix: "data:"})

type TransformerFunc

type TransformerFunc func(json, arg string) string

TransformerFunc is a function adapter that implements the Transformer interface. It allows any plain function with the signature func(json, arg string) string to satisfy Transformer without defining a named struct.

This mirrors the http.HandlerFunc pattern from the standard library.

Example:

fj.AddTransformer("upper", fj.TransformerFunc(func(json, arg string) string {
    return strings.ToUpper(json)
}))

func (TransformerFunc) Apply

func (f TransformerFunc) Apply(json, arg string) string

Apply calls f(json, arg), satisfying the Transformer interface.

type Type

type Type int

Type represents the different possible types for a JSON value. It is used to indicate the specific type of a JSON value, such as a string, number, boolean, etc.

const (
	// Null is a constant representing a JSON null value.
	// In JSON, null is used to represent the absence of a value.
	Null Type = iota
	// False is a constant representing a JSON false boolean value.
	// In JSON, false is a boolean value that represents a negative or off state.
	False
	// Number is a constant representing a JSON number value.
	// In JSON, numbers can be integers or floating-point values.
	Number
	// String is a constant representing a JSON string value.
	// In JSON, strings are sequences of characters enclosed in double quotes.
	String
	// True is a constant representing a JSON true boolean value.
	// In JSON, true is a boolean value that represents a positive or on state.
	True
	// JSON is a constant representing a raw JSON block.
	// This type can be used to represent any valid JSON object or array.
	JSON
)

func (Type) String

func (t Type) String() string

String provides a string representation of the `Type` enumeration.

This method converts the `Type` value into a human-readable string. It is particularly useful for debugging or logging purposes.

Mapping of `Type` values to strings:

  • Null: "Null"
  • False: "False"
  • Number: "Number"
  • String: "String"
  • True: "True"
  • JSON: "JSON"
  • Default (unknown type): An empty string is returned.

Returns:

  • string: A string representation of the `Type` value.

Example Usage:

var t Type = True
fmt.Println(t.String())  // Output: "True"

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL