gosonata

package module
v0.0.0-...-eeed0eb Latest Latest
Warning

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

Go to latest
Published: Apr 2, 2026 License: MIT Imports: 9 Imported by: 1

README

GoSonata

[!WARNING] This project was an experiment to test the use of the Copilot/Sonnet agent to build the Go version of JSONata. For a stable and maintained Go port, see gnata.

Go Version GoDoc

Go Report Card

A high-performance Go implementation of JSONata 2.1.0+, designed for intensive data streaming scenarios.

Status: ✅ Conformance complete — 1273/1273 official JSONata test suite cases + 249 imported conformance tests passing (100%)

Features

  • High Performance: Hand-written recursive descent parser optimized for speed
  • Concurrency: Native goroutine support for parallel evaluation (enabled by default)
  • Streaming: Efficient handling of large JSON documents
  • Spec Compliant: Target 100% compatibility with JSONata 2.1.0+ specification
  • Type Safe: Strongly typed with comprehensive error handling
  • Well Tested: 1273/1273 official JSONata test suite cases passing (102 groups, 100%), plus 249 additional imported conformance tests
  • Production Ready: DoS protection, resource limits, structured logging
  • WebAssembly: Browser, Node.js (js/wasm) and WASI runtime support

What is JSONata?

JSONata is a lightweight query and transformation language for JSON data. It allows you to:

  • Extract data from complex JSON structures
  • Transform data with powerful expressions
  • Combine, filter, sort and aggregate data
  • Perform calculations and string manipulations

Installation

go get github.com/sandrolain/gosonata

Requirements: Go 1.26 or later

Quick Start

Simple Evaluation
package main

import (
    "fmt"
    "log"

    "github.com/sandrolain/gosonata"
)

func main() {
    data := map[string]interface{}{
        "name": "John",
        "age":  30,
    }

    result, err := gosonata.Eval("$.name", data)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(result) // Output: John
}
Compile Once, Evaluate Many Times

For better performance when evaluating the same expression multiple times:

import (
    "context"

    "github.com/sandrolain/gosonata"
    "github.com/sandrolain/gosonata/pkg/evaluator"
)

// Compile the expression once
expr, err := gosonata.Compile("$.items[price > 100]")
if err != nil {
    log.Fatal(err)
}

ev := evaluator.New()
ctx := context.Background()

// Evaluate against different data
result1, _ := ev.Eval(ctx, expr, data1)
result2, _ := ev.Eval(ctx, expr, data2)
result3, _ := ev.Eval(ctx, expr, data3)
With Options
result, err := gosonata.Eval("$.items", data,
    gosonata.WithCaching(true),
    gosonata.WithConcurrency(false),
    gosonata.WithTimeout(5*time.Second),
    gosonata.WithDebug(true),
)
EvalBytes — zero-copy fast path

When working with raw JSON (e.g. from an HTTP body or a message queue), use EvalBytes to evaluate an expression directly against json.RawMessage without unmarshalling the entire document first.

raw := json.RawMessage(`{"user":{"email":"alice@example.com","role":"admin"},"price":99.99}`)

// Simple field extraction — never calls json.Unmarshal
result, err := gosonata.EvalBytes("user.email", raw)
// → "alice@example.com"

// Comparison predicate
result, err = gosonata.EvalBytes(`user.role = "admin"`, raw)
// → true

// Built-in function
result, err = gosonata.EvalBytes("$exists(user.email)", raw)
// → true
When the fast path is active

EvalBytes automatically detects at compile time whether the expression is eligible for zero-copy evaluation (the fast path). Eligible patterns are:

Pattern Example
Pure dotted-path navigation user.address.city
Equality / inequality vs a literal status = "active", code != 0
Single stdlib function on a path $exists(a.b), $lowercase(name), $sum(totals)

Supported fast-path functions: $exists, $not, $boolean, $string, $number, $lowercase, $uppercase, $trim, $length, $type, $abs, $floor, $ceil, $sqrt, $count, $sum, $max, $min, $average, $reverse, $keys, $distinct, $contains.

Expressions that do not match these patterns (predicates, wildcards, lambdas, etc.) fall back to standard full-AST evaluation automatically — there is nothing to configure.

You can introspect eligibility after compilation:

expr, _ := gosonata.Compile("user.email")
if expr.IsFastPath() {
    // will use zero-copy path in EvalBytes
}
Performance

Benchmarks on Apple M2 (Go 1.26, arm64), expression pre-compiled once:

Scenario EvalBytes ns/op Eval + json.Unmarshal ns/op Speedup
Pure path extraction ~345 ~2,053 ~6×
Comparison predicate ~364 ~2,053 ~5.6×
$exists check ~332 ~2,053 ~6.2×

Allocations drop from ~42 to ~6 per call for the pure-path case.

Examples

Extract Data
// Get all product names
result, _ := gosonata.Eval("$.products.name", data)

// Get names of products over $100
result, _ := gosonata.Eval("$.products[price > 100].name", data)
Transform Data
// Create new structure
query := `{
    "total": $sum(items.price),
    "count": $count(items),
    "names": items.name
}`
result, _ := gosonata.Eval(query, data)
Aggregate Data
// Calculate total and average
query := `{
    "total": $sum(sales.amount),
    "average": $average(sales.amount),
    "max": $max(sales.amount)
}`
result, _ := gosonata.Eval(query, data)

For more examples, see the examples/ directory.

Documentation

Project documentation (in docs/)
  • docs/ARCHITECTURE.md - Package structure, core components, data flow, performance and concurrency model
  • docs/API.md - Public API reference, options, usage patterns and examples
  • docs/DIFFERENCES.md - Implementation differences vs JS reference, known limitations and workarounds
  • docs/WASM.md - WebAssembly integration: browser, Node.js, WASI

Testing

Run the tests:

# All tests
task test

# Unit tests only
task test:unit

# With coverage
task coverage

# Run conformance tests (official JSONata test suite — 1273 cases in 102 groups)
task test:conformance

# Run GoSonata vs JSONata JS comparison report
task bench:comparison:report

Performance

GoSonata is designed for high performance. All benchmarks run on Apple M2 (arm64), Go 1.26.

# Run all benchmarks
task bench

# Parser benchmarks
task bench:parse

# Evaluator benchmarks
task bench:eval

# GoSonata vs JSONata JS comparison report
task bench:comparison:report
Parser benchmarks

All operations measured with a fresh compile (no cache). The NodeArena allocator pre-allocates a 16 KB chunk per expression, which increases reported B/op but dramatically reduces allocs/op.

Expression ns/op B/op allocs/op Δ allocs vs baseline
Simple path ($.name) 2,254 16,728 8
Complex path ($.users[0].address.city) 3,020 16,920 21 −43%
With functions ($sum($.items.price)) 2,817 16,888 19 −42%
Nested lambda 3,210 16,944 23 −44%
Object transformation 3,548 16,992 26 −45%

B/op is dominated by the NodeArena 16 KB pre-allocation (64-node bump-pointer chunk). Total live memory per expression is similar; the arena just batches allocations into one.

Evaluator benchmarks — pre-compiled expression
Scenario ns/op B/op allocs/op Δ ns/op vs baseline
Simple path (1 user) 533 568 9 −9%
Filter (10 users) 793 632 10 −10%
Filter (100 users) 847 632 10 −15%
Filter (1000 users) 849 632 10 −17%
Aggregation (100 users) 826 648 11 −18%
Transform (100 users) 1,530 1,856 30 −11%
Sort (100 users) 767 808 18 −11%
Arithmetic expression 793 544 13 −18%
Concurrent eval (100 users) 329 632 10 −15%

Filter, aggregation and sort evaluation cost stays nearly constant from 10 to 1000 items thanks to lazy path resolution. Baseline: initial implementation before the OPT-01…OPT-14 optimization sprint (2026-02-26).

GoSonata vs JSONata JS (reference implementation)

Eval-only comparison (expression pre-compiled on both sides, data in native format). Each scenario is verified to produce identical results in both engines (TestResultCorrectness). GoSonata numbers updated after the OPT-01…OPT-14 sprint (2026-02-26); JS numbers unchanged.

Scenario GoSonata ns/op JSONata JS ns/op Speedup
SimplePath / 1 user ~770 ~1,420 ~1.8×
Filter / 10 users ~2,900 ~10,940 ~3.8×
Filter / 100 users ~15,700 ~104,400 ~6.6×
Filter / 1000 users ~143,000 ~960,100 ~6.7×
Aggregation / 10 users ~1,300 ~3,480 ~2.7×
Aggregation / 100 users ~4,100 ~19,280 ~4.7×
Transform / 10 users ~2,400 ~10,070 ~4.2×
Transform / 100 users ~13,300 ~45,760 ~3.4×
Sort / 10 users ~5,900 ~44,300 ~7.5×
Sort / 100 users ~63,000 ~823,100 ~13.1×
Arithmetic ~840 ~2,610 ~3.1×

JSONata JS timings measured within a single persistent Node.js process (no startup cost). JS evaluate() is inherently async (Promise); Go is synchronous — the async overhead is included since it is unavoidable in real JS usage. GoSonata estimates scaled from pre-sprint baseline using the per-scenario improvement ratios measured in tests/benchmark. Run task bench:comparison:report to regenerate exact values.

WebAssembly

GoSonata can be compiled to WebAssembly for use in browsers, Node.js, and WASI runtimes.

Build
# Build browser / Node.js target (GOOS=js GOARCH=wasm)
task wasm:build:js

# Build WASI runtime target (GOOS=wasip1 GOARCH=wasm)
task wasm:build:wasi

# Copy wasm_exec.js support file
task wasm:copy-support:js
Use in Node.js
require('./wasm_exec.js');
const fs = require('fs');
const go = new Go();
const { instance } = await WebAssembly.instantiate(
  fs.readFileSync('./gosonata.wasm'), go.importObject
);
go.run(instance);
const result = JSON.parse(globalThis.gosonata.eval(
  '$.users[age > 25].name',
  JSON.stringify({ users: [...] })
));
Use in Browser
<script src="wasm_exec.js"></script>
<script>
  const go = new Go();
  WebAssembly.instantiateStreaming(fetch('gosonata.wasm'), go.importObject)
    .then(({ instance }) => {
      go.run(instance);
      const result = JSON.parse(
        globalThis.gosonata.eval('$.name', JSON.stringify({ name: 'Alice' }))
      );
    });
</script>
Use via WASI (stdin/stdout JSON protocol)
echo '{"query":"$.name","data":{"name":"Alice"}}' | wasmtime cmd/wasm/wasi/gosonata.wasm
# → {"result":"Alice"}

See docs/WASM.md and examples/wasm/ for full documentation.

WASM performance

GoSonata WASM runs via the Go runtime embedded in the binary. Measured on Apple M2, Go 1.26, Node.js v24:

Scenario Go ns/op JS ns/op WASM ns/op
SimplePath 736 945 19,695
Filter / 10 users 2,599 10,861 72,124
Filter / 100 users 18,836 121,162 531,210
Aggregation / 100 users 5,226 19,861 445,592
Sort / 10 users 6,974 44,368 147,293

Native Go is ~18–85× faster than WASM. Use WASM for browser/non-Go environments; prefer native Go for backend services.


Security

Security is a priority. GoSonata includes:

  • DoS protection (depth limits, timeouts, range limits)
  • Input validation and sanitization
  • Resource limits and monitoring
  • Regular security scans (gosec, trivy)

To run security scans:

task security

License

MIT License - Copyright (c) 2026 Sandro Lain

Acknowledgments

  • JSONata - Original specification and reference implementation
  • jsonata-js - JavaScript reference implementation
  • go-jsonata - Go transliteration inspiration

Support


Made with ❤️ for the Go community

Documentation

Overview

Package gosonata provides a high-performance Go implementation of JSONata 2.1.0+.

JSONata is a lightweight query and transformation language for JSON data. GoSonata is designed for intensive data streaming scenarios with focus on:

  • Performance: Optimized parser and evaluator
  • Concurrency: Native goroutine support
  • Streaming: Handle large JSON documents efficiently
  • Conformance: 100% compatibility with JSONata 2.1.0+ spec

Quick Start

// Simple evaluation
result, err := gosonata.Eval("$.name", data)

// Compile once, evaluate many times
expr, err := gosonata.Compile("$.items[price > 100]")
ev := evaluator.New()
result1, _ := ev.Eval(ctx, expr, data1)
result2, _ := ev.Eval(ctx, expr, data2)

// With options
result, err := gosonata.Eval("$.items", data,
    gosonata.WithCaching(true),
    gosonata.WithTimeout(5*time.Second),
)

Performance

GoSonata is optimized for:

  • Fast parsing with hand-written recursive descent parser
  • Efficient evaluation with minimal allocations
  • Concurrent evaluation for independent expressions
  • Optional caching for repeated queries
  • Streaming support for large documents

Conformance

GoSonata aims for 100% compatibility with JSONata 2.1.0+ specification, passing the complete official test suite (91+ test groups).

More Information

For detailed documentation, see:

  • Parser: github.com/sandrolain/gosonata/pkg/parser
  • Evaluator: github.com/sandrolain/gosonata/pkg/evaluator
  • Functions: github.com/sandrolain/gosonata/pkg/functions
  • Types: github.com/sandrolain/gosonata/pkg/types

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Compile

func Compile(query string, opts ...parser.CompileOption) (*types.Expression, error)

Compile compiles a JSONata expression for repeated evaluation.

The compiled expression can be evaluated multiple times against different data. It is safe for concurrent use.

Example:

expr, err := gosonata.Compile("$.items[price > 100]")
if err != nil {
    log.Fatal(err)
}
result, _ := expr.Eval(ctx, data)

func Eval

func Eval(query string, data interface{}, opts ...evaluator.EvalOption) (interface{}, error)

Eval is a convenience function that compiles and evaluates an expression in a single call.

For repeated evaluations of the same expression, use Compile instead. If WithCaching(true) is passed in opts, the compiled expression is cached and reused on subsequent calls with the same query string.

Example:

result, err := gosonata.Eval("$.name", data)

func EvalBytes

func EvalBytes(query string, raw json.RawMessage, opts ...EvalOption) (any, error)

EvalBytes is a convenience function that compiles query and evaluates it against raw JSON bytes in a single call.

For expressions eligible for the zero-copy fast path (simple field access, equality comparisons, and supported stdlib functions), this avoids a full json.Unmarshal. Call expr.IsFastPath() on a pre-compiled expression to check whether the fast path applies.

Example:

result, err := gosonata.EvalBytes(`user.email`, rawJSON)

func EvalBytesWithContext

func EvalBytesWithContext(ctx context.Context, query string, raw json.RawMessage, opts ...EvalOption) (any, error)

EvalBytesWithContext evaluates an expression against raw JSON bytes with a custom context.

func EvalStream

func EvalStream(ctx context.Context, query string, r io.Reader, opts ...EvalOption) (<-chan StreamResult, error)

EvalStream compiles query and evaluates it against each JSON value read from r.

It is a convenience wrapper around Compile + Evaluator.EvalStream. See evaluator.EvalStream for full documentation.

func EvalWithContext

func EvalWithContext(ctx context.Context, query string, data interface{}, opts ...evaluator.EvalOption) (interface{}, error)

EvalWithContext evaluates an expression with a custom context.

func MustCompile

func MustCompile(query string) *types.Expression

MustCompile is like Compile but panics if the expression cannot be compiled. It simplifies safe initialization of global variables.

func Version

func Version() string

Version returns the current version of GoSonata.

Types

type AdvancedCustomFunc

type AdvancedCustomFunc = functions.AdvancedCustomFunc

AdvancedCustomFunc is the signature for higher-order user-defined functions that receive a Caller to invoke function arguments from JSONata expressions.

type AdvancedCustomFunctionDef

type AdvancedCustomFunctionDef = functions.AdvancedCustomFunctionDef

AdvancedCustomFunctionDef is a type alias for functions.AdvancedCustomFunctionDef, re-exported so callers only need to import the top-level gosonata package.

type CustomFunc

type CustomFunc = functions.CustomFunc

CustomFunc is the signature for user-defined functions callable from JSONata expressions. See WithCustomFunction.

type CustomFunctionDef

type CustomFunctionDef = functions.CustomFunctionDef

CustomFunctionDef is a type alias for functions.CustomFunctionDef, re-exported so callers only need to import the top-level gosonata package.

type EvalOption

type EvalOption = evaluator.EvalOption

EvalOption is a type alias for evaluator.EvalOption so callers do not need to import the evaluator package directly.

func WithCacheSize

func WithCacheSize(size int) EvalOption

WithCacheSize re-exports evaluator.WithCacheSize for convenience.

func WithCaching

func WithCaching(enabled bool) EvalOption

WithCaching re-exports evaluator.WithCaching for convenience.

func WithConcurrency

func WithConcurrency(enabled bool) EvalOption

WithConcurrency re-exports evaluator.WithConcurrency for convenience.

func WithCustomFunction

func WithCustomFunction(name, signature string, fn CustomFunc) EvalOption

WithCustomFunction registers a user-defined function with name (without "$") and an optional JSONata type-signature string.

Example:

result, err := gosonata.Eval(`$greet("World")`, nil,
    gosonata.WithCustomFunction("greet", "<s:s>", func(ctx context.Context, args ...interface{}) (interface{}, error) {
        return "Hello, " + args[0].(string) + "!", nil
    }),
)

func WithDebug

func WithDebug(enabled bool) EvalOption

WithDebug re-exports evaluator.WithDebug for convenience.

func WithFunctions

func WithFunctions(defs ...functions.FunctionEntry) EvalOption

WithFunctions registers any mix of CustomFunctionDef and AdvancedCustomFunctionDef in a single variadic call. Use it to spread the result of AllEntries() from any ext sub-package:

gosonata.WithFunctions(extstring.AllEntries()...)
gosonata.WithFunctions(extarray.AllEntries()...)
gosonata.WithFunctions(ext.AllEntries()...)

func WithTimeout

func WithTimeout(t time.Duration) EvalOption

WithTimeout re-exports evaluator.WithTimeout for convenience.

type FunctionEntry

type FunctionEntry = functions.FunctionEntry

FunctionEntry is a type alias for functions.FunctionEntry, the common interface implemented by both functions.CustomFunctionDef and functions.AdvancedCustomFunctionDef. It allows mixing both kinds in a single call to WithFunctions.

type StreamResult

type StreamResult = evaluator.StreamResult

StreamResult re-exports evaluator.StreamResult for callers that only import gosonata.

Directories

Path Synopsis
cmd
wasm/js command
Command gosonata-wasm-js is the WebAssembly entrypoint for browser and Node.js.
Command gosonata-wasm-js is the WebAssembly entrypoint for browser and Node.js.
pkg
cache
Package cache provides a thread-safe expression cache for compiled JSONata expressions.
Package cache provides a thread-safe expression cache for compiled JSONata expressions.
ext
Package ext provides optional extension functions for GoSonata that go beyond the official JSONata 2.1.0+ specification.
Package ext provides optional extension functions for GoSonata that go beyond the official JSONata 2.1.0+ specification.
ext/extarray
Package extarray provides extended array functions for GoSonata beyond the official JSONata spec.
Package extarray provides extended array functions for GoSonata beyond the official JSONata spec.
ext/extcrypto
Package extcrypto provides cryptographic and hashing functions for GoSonata.
Package extcrypto provides cryptographic and hashing functions for GoSonata.
ext/extdatetime
Package extdatetime provides extended date/time functions for GoSonata beyond the official JSONata spec.
Package extdatetime provides extended date/time functions for GoSonata beyond the official JSONata spec.
ext/extformat
Package extformat provides data-format functions for GoSonata (CSV, templates).
Package extformat provides data-format functions for GoSonata (CSV, templates).
ext/extfunc
Package extfunc provides functional programming utilities for GoSonata beyond the official JSONata spec.
Package extfunc provides functional programming utilities for GoSonata beyond the official JSONata spec.
ext/extnumeric
Package extnumeric provides extended numeric functions for GoSonata beyond the official JSONata spec.
Package extnumeric provides extended numeric functions for GoSonata beyond the official JSONata spec.
ext/extobject
Package extobject provides extended object functions for GoSonata beyond the official JSONata spec.
Package extobject provides extended object functions for GoSonata beyond the official JSONata spec.
ext/extstring
Package extstring provides extended string functions for GoSonata beyond the official JSONata spec.
Package extstring provides extended string functions for GoSonata beyond the official JSONata spec.
ext/exttypes
Package exttypes provides type predicate and control functions for GoSonata beyond the official JSONata spec.
Package exttypes provides type predicate and control functions for GoSonata beyond the official JSONata spec.
ext/extutil
Package extutil provides shared helpers for the ext sub-packages.
Package extutil provides shared helpers for the ext sub-packages.
functions
Package functions provides types for registering custom JSONata functions.
Package functions provides types for registering custom JSONata functions.
stream
Package stream provides a high-throughput multi-expression evaluator.
Package stream provides a high-throughput multi-expression evaluator.
types
Package types defines the core type system for GoSonata.
Package types defines the core type system for GoSonata.
tests

Jump to

Keyboard shortcuts

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