jsonkit

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Feb 23, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

README

JSONKit

JSONKit is a tooling-grade JSON-family engine designed to be embedded inside language servers, compilers, and editor toolchains.

Release stage: v0.x series (CHANGELOG). The public facade and experimental engine are functional with comprehensive test coverage. The v0.x series allows API refinement based on real-world usage before committing to v1.0 stability guarantees.

For detailed release notes, see GitHub Releases.

Quick Start

Installation
go get github.com/forgemechanic/jsonkit

Then import into your code

import "github.com/forgemechanic/jsonkit"
Simple Usage (Drop-in for encoding/json)
import "github.com/forgemechanic/jsonkit"

// Unmarshal JSON to a struct
var config struct {
    Name string `json:"name"`
    Port int    `json:"port"`
}
err := jsonkit.Unmarshal([]byte(`{"name":"api","port":8080}`), &config)

// Marshal struct to JSON
data, err := jsonkit.Marshal(config)
Parse with Comments (JSONC)
import "github.com/forgemechanic/jsonkit"

data := []byte(`{
    // Configuration file
    "name": "api",
    "port": 8080,  // trailing comma ok
}`)

var config map[string]any
res, err := jsonkit.UnmarshalWithOptions(
    data, 
    &config,
    jsonkit.WithDecodeProfile(jsonkit.ProfileJSONC),
)
Embedded JSON in Another Language
import (
    "github.com/forgemechanic/jsonkit"
    "github.com/forgemechanic/jsonkit/exp/source"
)

// Parse JSON embedded in a host document
host := []byte("config = {name:'app', port:3000} end")
src := source.NewBytes("host.txt", host)

res := jsonkit.Parse(
    src,
    jsonkit.WithProfile(jsonkit.ProfileJSON5),
    jsonkit.WithEmbeddedBlock(jsonkit.EmbeddedBlock{
        Start: 9,   // offset of '{'
        End:   32,  // offset after '}'
        UseHostOffsets: true,
    }),
)

if res.OK() {
    // Diagnostics have correct host document positions
    fmt.Println("Valid embedded JSON")
}
For 90% of Use Cases

Just use jsonkit.Unmarshal() and jsonkit.Marshal() — they work like encoding/json with better diagnostics. The advanced features (CST, retention modes, embedded parsing) are for LSP authors and compiler writers.

Why JSONKit?

Most JSON libraries parse text into values and throw the parse tree away. JSONKit keeps the full concrete syntax tree (CST) — comments, whitespace, spans, diagnostics — and lets you project Go values from it without reparsing. This makes it the right foundation when you need more than just deserialization:

  • Embedded parsing inside another language. JSONKit can parse a bounded JSON block within a host document (e.g., a config literal inside a DSL), remap spans back to host coordinates, and report diagnostics that make sense in the host's error model. This embedded-parsing capability is the original reason JSONKit exists.

  • LSP / editor integration. A language server that already has a parse tree can decode JSON values directly from CST nodes — skipping the "serialize to text, reparse, deserialize" round-trip that encoding/json would require. Incremental reparsing, trivia-preserving formatting, and stable diagnostic codes complete the editor story.

  • Tooling-grade validation. Retention modes let you choose exactly how much parse state to keep: full CST for formatting, tokens-only for syntax highlighting, structural for schema validation, or validate-only for the fastest possible error check — all through the same API.

If your use case is "read a config file into a struct," encoding/json is fine. JSONKit is for when the parse tree is the product, not a throwaway intermediate.

What is implemented

  • Stable facade package: github.com/forgemechanic/jsonkit
  • Compatibility package: github.com/forgemechanic/jsonkit/compat/json
  • Standalone JSONL package: github.com/forgemechanic/jsonkit/jsonl
  • Parser profiles: strict JSON (RFC 8259), JSONC (comments + trailing commas), JSON5 (extended)
  • Retention modes:
    • RetentionFullCST — full lossless tree
    • RetentionTokens — token stream with trivia
    • RetentionStructural — lightweight structural skeleton
    • RetentionValidateOnly — fastest validation, no tree retained
    • RetentionWindowedCST — supported; without EmbeddedBlock it matches full-document CST retention, and with EmbeddedBlock it retains/parses only that bounded window
  • Embedded JSON bounded parsing (WithEmbeddedBlock) with host-offset remapping, terminator detection, and diagnostic hooks
  • Decode APIs:
    • compatibility-first: Unmarshal, Valid, NewDecoder
    • advanced: UnmarshalWithOptions, NewDecoderWithOptions
    • decode operates on the existing parse tree — no reparse required
  • Encode APIs:
    • compatibility-first: Marshal, MarshalIndent, NewEncoder
    • advanced: MarshalWithOptions, NewEncoderWithOptions
    • JSON5-style controls: quote style, unquoted keys, trailing commas, emitted comments
  • Lossless printer: roundtrips valid input byte-for-byte from CST
  • CST-based formatter with dialect-safe defaults and comment policy
  • Incremental parsing sessions with edit mapping and subtree reuse
  • Semantic projection with JSON Pointer paths, derived from CST without reparsing
  • Deterministic stress model fixtures + stress benchmark corpus integration

Public packages

Root facade (jsonkit) is the primary import surface:

  • Parse and retention controls (Parse, WithProfile, WithRetentionMode)
  • Embedded parsing (WithEmbeddedBlock)
  • Decode/encode entrypoints
  • The decode path works directly from the parse tree — when you already have a CST (e.g., from an LSP parse), decode projects Go values from it with no second parse pass

Compatibility surface (compat/json) is optional:

  • stdlib-shaped wrappers for lower-friction migration from encoding/json
  • advanced JSONKit knobs intentionally stay in root package

JSONL surface (jsonl) is separate by design:

  • record indexing and lazy per-record parse/projection APIs

Performance and benchmark harness

Performance Characteristics

JSONKit is optimized for correctness and tooling features over raw throughput. Performance varies significantly by retention mode:

When JSONKit is competitive or faster:

  • Validation-only mode — approaches or exceeds many popular libraries when you only need error checking
  • Standard decode — comparable to encoding/json for decoding to map[string]any or structs
  • Already-parsed workflows — zero-cost decode when you already have a CST from editor/LSP operations (no other library offers this)
  • Embedded block parsing — unique capability; bounded parsing avoids processing entire host documents

When JSONKit is slower:

  • Full CST retention — allocates and preserves complete parse trees; 2-4x slower than validation-only
  • vs SIMD-optimized libraries — sonic and segmentio use assembly optimizations; they're 3-12x faster for validation
  • High-throughput ingestion — if you're processing millions of JSON documents/sec and don't need parse trees, use sonic

Trade-offs by use case:

  • LSP/editor tooling → Use JSONKit. You need the CST, diagnostics, and span precision.
  • Validation gates (CI/ingestion) → Use JSONKit's RetentionValidateOnly for competitive speed with better diagnostics, or sonic/segmentio for maximum throughput.
  • Runtime config parsing → Use encoding/json or JSONKit's compatibility mode. Performance is nearly identical.
  • Log processing at scale → Use sonic. Raw speed matters more than parse tree fidelity.

The retention mode system lets you explicitly choose the speed/fidelity trade-off for each parse operation.

Benchmark Harness

Cross-library benchmark harness lives in tools/bench/jsonbench.

  • Adapters include jsonkit, stdjson, goccy, jsoniter, segmentio, and sonic
  • JSONKit mode adapters include full, validate-only, structural, and tokens tracks
  • Stress corpus support is integrated (JSON_BENCH_CORPUS=stress|all)

Common commands:

task bench:json:smoke
task bench:json
task bench:json:compare
task testgen:stress
task testgen:stress:dialects

Detailed benchmark usage: docs/benchmarking-json.md

Documentation map and lifecycle

Active specifications (normative):

  • architecture.md — core invariants, layer responsibilities, alignment matrix
  • testplan.md — quality gates and test strategy
  • testfilegen.md — test data generation strategy
  • go-surface.md — package layout, stability ladder, export strategy

Suggested read order for contributors:

  1. architecture.md
  2. go-surface.md
  3. docs/retention-modes-api.md
  4. docs/use-cases-performance.md
  5. docs/benchmarking-json.md
  6. docs/diagnostic-codes.md

Active references:

  • docs/benchmarking-json.md — cross-library benchmark harness usage
  • docs/use-cases-performance.md — performance-oriented use cases
  • docs/retention-modes-api.md — retention mode selection and trade-offs
  • docs/diagnostic-codes.md — stable diagnostic code families and contract policy
  • docs/README.md — doc lifecycle/status index

Reference summaries:

  • docs/history-summary.md
  • docs/history-api-summary.md
  • docs/history-benchmark-summary.md

Archive (frozen records):

  • docs/archive/README.md (archive index)

Stability model

JSONKit uses a three-tier "stability ladder" so that tooling builders can access the full engine while the facade remains stable:

  • jsonkit root facade: stable API track, semver-safe within major versions
  • exp/*: public experimental engine — importable for LSP/compiler/tooling builders, may evolve across minor versions
  • internal/*: private internals, no compatibility guarantees

This means an LSP can import exp/parse, exp/sem, and exp/source directly to access CST nodes, semantic projection, and incremental editing — without being locked into facade-level abstractions that might not expose enough control.

See go-surface.md for the full design rationale and export strategy.

Development Approach

This project was developed using AI-assisted pair programming with human oversight on architecture and design decisions. The approach:

  • Architecture-first design — Human-authored specifications (architecture.md, testplan.md, testfilegen.md, go-surface.md) define system invariants and behavior
  • AI-assisted implementation — Implementation follows specifications with AI code generation guided by architectural constraints (see AGENTS.md)
  • Comprehensive validation — Cross-library benchmarks, golden test suites, and production use-case validation

Quality assurance metrics:

  • ✅ 80%+ main package coverage, 90%+ on public API entrypoints (parse, decode, encode, options)
  • ✅ Experimental packages: 80-100% coverage (profile/span/diag at 100%)
  • ✅ Cross-library benchmarks vs stdlib, sonic, goccy, jsoniter, segmentio
  • ✅ Deterministic test generation with fault injection
  • ✅ Validated against production embedded-parsing use case

The AI-assisted development model enables rapid implementation of complex specifications while maintaining architectural discipline through explicit invariants and comprehensive testing.

Documentation

Overview

Package jsonkit provides the stable facade for parsing, validating, decoding, and encoding JSON-family documents (strict JSON, JSONC, JSON5).

JSONKit is designed to be embedded inside language servers, compilers, and editor toolchains. It preserves a full concrete syntax tree (CST) with comments, whitespace, and source spans, and projects Go values from the parse tree without reparsing. The decode path operates on the existing CST, so a host system that already parsed JSON for editor features can extract typed values with no second parse pass.

The facade is intentionally small and semver-stable within major versions. Advanced engine APIs (lexer, parser, CST, semantic projection, incremental sessions) live under exp/* for direct use by LSP and tooling builders.

Release stage: v0.x series. The API may evolve across minor versions during v0.x based on real-world usage before committing to v1.0 stability guarantees.

Index

Examples

Constants

View Source
const Version = "0.1.0"

Version is the current JSONKit version.

This follows semantic versioning (https://semver.org/). During the v0.x series, the API may evolve across minor versions.

Variables

This section is empty.

Functions

func Marshal

func Marshal(v any) ([]byte, error)

Marshal encodes v using strict JSON defaults.

Example
package main

import (
	"fmt"

	jsonkit "github.com/forgemechanic/jsonkit"
)

func main() {
	out, err := jsonkit.Marshal(struct {
		ID int `json:"id"`
	}{ID: 7})

	fmt.Println(err == nil)
	fmt.Println(string(out))
}
Output:

true
{"id":7}

func MarshalIndent

func MarshalIndent(v any, prefix, indent string) ([]byte, error)

MarshalIndent encodes v with pretty-print indentation.

Example
package main

import (
	"fmt"

	jsonkit "github.com/forgemechanic/jsonkit"
)

func main() {
	out, err := jsonkit.MarshalIndent(struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	}{ID: 7, Name: "jsonkit"}, "", "  ")

	fmt.Println(err == nil)
	fmt.Println(string(out))
}
Output:

true
{
  "id": 7,
  "name": "jsonkit"
}

func MarshalWithOptions

func MarshalWithOptions(v any, opts ...EncodeOption) ([]byte, error)

MarshalWithOptions encodes v using explicit profile/options.

Example
package main

import (
	"fmt"

	jsonkit "github.com/forgemechanic/jsonkit"
)

func main() {
	type payload struct {
		Message string `json:"message"`
		OK      bool   `json:"ok"`
	}

	out, err := jsonkit.MarshalWithOptions(
		payload{Message: "hello", OK: true},
		jsonkit.WithEncodeProfile(jsonkit.ProfileJSON5),
		jsonkit.WithEncodeUnquotedKeys(true),
		jsonkit.WithEncodeStringQuoteStyle(jsonkit.EncodeQuoteSingle),
		jsonkit.WithEncodeTrailingCommas(true),
	)

	fmt.Println(err == nil)
	fmt.Println(string(out))
}
Output:

true
{message:'hello',ok:true,}
Example (LeadingComment)
package main

import (
	"fmt"

	jsonkit "github.com/forgemechanic/jsonkit"
)

func main() {
	out, err := jsonkit.MarshalWithOptions(
		struct {
			ID int `json:"id"`
		}{ID: 1},
		jsonkit.WithEncodeProfile(jsonkit.ProfileJSON5),
		jsonkit.WithEncodeLeadingComment("generated", jsonkit.EncodeCommentLine),
		jsonkit.WithEncodeUnquotedKeys(true),
	)

	fmt.Println(err == nil)
	fmt.Println(string(out))
}
Output:

true
// generated
{id:1}

func Unmarshal

func Unmarshal(data []byte, v any) error

Unmarshal decodes strict JSON into v.

Example
package main

import (
	"fmt"

	jsonkit "github.com/forgemechanic/jsonkit"
)

func main() {
	var dst struct {
		Name string `json:"name"`
	}
	err := jsonkit.Unmarshal([]byte(`{"name":"jsonkit"}`), &dst)

	fmt.Println(dst.Name)
	fmt.Println(err == nil)
}
Output:

jsonkit
true

func Valid

func Valid(data []byte) bool

Valid reports whether data is valid strict JSON.

Example
package main

import (
	"fmt"

	jsonkit "github.com/forgemechanic/jsonkit"
)

func main() {
	fmt.Println(jsonkit.Valid([]byte(`{"ok":true}`)))
	fmt.Println(jsonkit.Valid([]byte(`{ok:true}`)))
}
Output:

true
false

Types

type CST

type CST = cst.Tree

CST is the facade alias for exp/cst.Tree.

type DecodeError

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

DecodeError wraps an underlying decode error with optional diagnostics.

Example
package main

import (
	"errors"
	"fmt"

	jsonkit "github.com/forgemechanic/jsonkit"
)

func main() {
	var dst map[string]any
	_, err := jsonkit.UnmarshalWithOptions(
		[]byte(`{"name": }`),
		&dst,
		jsonkit.WithDecodeDiagnostics(true),
	)

	var derr *jsonkit.DecodeError
	fmt.Println(errors.As(err, &derr))
	fmt.Println(len(derr.Diagnostics()) > 0)
}
Output:

true
true

func (*DecodeError) Diagnostics

func (e *DecodeError) Diagnostics() []Diagnostic

Diagnostics returns a copy of attached diagnostics.

func (*DecodeError) Error

func (e *DecodeError) Error() string

Error implements error.

func (*DecodeError) Unwrap

func (e *DecodeError) Unwrap() error

Unwrap returns the underlying decode error.

type DecodeOption

type DecodeOption func(*decodeConfig)

DecodeOption configures UnmarshalWithOptions and NewDecoderWithOptions.

func WithDecodeDiagnostics

func WithDecodeDiagnostics(enabled bool) DecodeOption

WithDecodeDiagnostics enables diagnostic capture in DecodeResult and DecodeError.

func WithDecodeDisallowUnknownFields

func WithDecodeDisallowUnknownFields() DecodeOption

WithDecodeDisallowUnknownFields matches encoding/json unknown-field behavior when decoding into structs.

func WithDecodeEmbeddedBlock

func WithDecodeEmbeddedBlock(block EmbeddedBlock) DecodeOption

WithDecodeEmbeddedBlock decodes a JSON fragment within a host range.

func WithDecodeProfile

func WithDecodeProfile(id ProfileID) DecodeOption

WithDecodeProfile selects the decode profile preset.

func WithDecodeStrictNativeBinder

func WithDecodeStrictNativeBinder() DecodeOption

WithDecodeStrictNativeBinder forces strict-profile decode to run through JSONKit's native parse+bind path instead of the stdlib fast path.

This is intended for parity/performance development and benchmarking of the native binder track.

func WithDecodeUseNumber

func WithDecodeUseNumber() DecodeOption

WithDecodeUseNumber configures interface-number decoding as json.Number.

type DecodeResult

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

DecodeResult carries optional metadata from UnmarshalWithOptions.

Example
package main

import (
	"fmt"

	jsonkit "github.com/forgemechanic/jsonkit"
)

func main() {
	var dst struct {
		Name string `json:"name"`
	}
	res, err := jsonkit.UnmarshalWithOptions(
		[]byte(`{name:'jsonkit'}`),
		&dst,
		jsonkit.WithDecodeProfile(jsonkit.ProfileJSON5),
		jsonkit.WithDecodeDiagnostics(true),
	)

	fmt.Println(err == nil)
	fmt.Println(res.Profile() == jsonkit.ProfileJSON5)
	fmt.Println(res.Mode() == jsonkit.RetentionFullCST)
}
Output:

true
true
true

func UnmarshalWithOptions

func UnmarshalWithOptions(data []byte, v any, opts ...DecodeOption) (DecodeResult, error)

UnmarshalWithOptions decodes input with profile/options and returns metadata.

Example
package main

import (
	"fmt"

	jsonkit "github.com/forgemechanic/jsonkit"
)

func main() {
	var dst struct {
		Name string `json:"name"`
	}
	res, err := jsonkit.UnmarshalWithOptions(
		[]byte(`{name:'jsonkit'}`),
		&dst,
		jsonkit.WithDecodeProfile(jsonkit.ProfileJSON5),
	)

	fmt.Println(dst.Name)
	fmt.Println(err == nil)
	fmt.Println(res.Profile() == jsonkit.ProfileJSON5)
}
Output:

jsonkit
true
true
Example (Diagnostics)
package main

import (
	"errors"
	"fmt"

	jsonkit "github.com/forgemechanic/jsonkit"
)

func main() {
	var dst map[string]any
	_, err := jsonkit.UnmarshalWithOptions(
		[]byte(`{"name": }`),
		&dst,
		jsonkit.WithDecodeDiagnostics(true),
	)

	var derr *jsonkit.DecodeError
	fmt.Println(err != nil)
	fmt.Println(errors.As(err, &derr))
	fmt.Println(len(derr.Diagnostics()) > 0)
}
Output:

true
true
true
Example (DisallowUnknownFields)
package main

import (
	"fmt"

	jsonkit "github.com/forgemechanic/jsonkit"
)

func main() {
	type payload struct {
		Name string `json:"name"`
	}

	var dst payload
	_, err := jsonkit.UnmarshalWithOptions(
		[]byte(`{"name":"jsonkit","extra":1}`),
		&dst,
		jsonkit.WithDecodeDisallowUnknownFields(),
	)

	fmt.Println(err != nil)
}
Output:

true
Example (UseNumber)
package main

import (
	"fmt"

	jsonkit "github.com/forgemechanic/jsonkit"
)

func main() {
	var dst map[string]any
	_, err := jsonkit.UnmarshalWithOptions(
		[]byte(`{"n":1}`),
		&dst,
		jsonkit.WithDecodeUseNumber(),
	)

	fmt.Println(err == nil)
	fmt.Printf("%T\n", dst["n"])
}
Output:

true
json.Number

func (DecodeResult) Diagnostics

func (r DecodeResult) Diagnostics() []Diagnostic

Diagnostics returns decode diagnostics when diagnostics capture is enabled.

func (DecodeResult) Embedded

func (r DecodeResult) Embedded() (EmbeddedMetadata, bool)

Embedded returns embedded-range metadata when embedded decoding is active.

func (DecodeResult) Mode

func (r DecodeResult) Mode() RetentionMode

Mode returns the effective parse retention mode used during decode.

func (DecodeResult) Profile

func (r DecodeResult) Profile() ProfileID

Profile returns the effective decode profile.

type Decoder

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

Decoder provides stream-style decode similar to encoding/json.Decoder.

Example
package main

import (
	"fmt"
	"strings"

	jsonkit "github.com/forgemechanic/jsonkit"
)

func main() {
	dec := jsonkit.NewDecoder(strings.NewReader(`{"id":3}`))
	var dst map[string]int
	err := dec.Decode(&dst)

	fmt.Println(err == nil)
	fmt.Println(dst["id"])
}
Output:

true
3

func NewDecoder

func NewDecoder(r io.Reader) *Decoder

NewDecoder creates a stream decoder with strict defaults.

func NewDecoderWithOptions

func NewDecoderWithOptions(r io.Reader, opts ...DecodeOption) *Decoder

NewDecoderWithOptions creates a stream decoder with explicit options.

Example
package main

import (
	"fmt"
	"strings"

	jsonkit "github.com/forgemechanic/jsonkit"
)

func main() {
	dec := jsonkit.NewDecoderWithOptions(
		strings.NewReader(`{name:'jsonkit'}`),
		jsonkit.WithDecodeProfile(jsonkit.ProfileJSON5),
	)
	var dst struct {
		Name string `json:"name"`
	}
	err := dec.Decode(&dst)

	fmt.Println(err == nil)
	fmt.Println(dst.Name)
}
Output:

true
jsonkit

func (*Decoder) Decode

func (d *Decoder) Decode(v any) error

Decode reads and decodes one JSON value into v.

func (*Decoder) DisallowUnknownFields

func (d *Decoder) DisallowUnknownFields()

DisallowUnknownFields rejects unknown object fields for struct targets.

func (*Decoder) UseNumber

func (d *Decoder) UseNumber()

UseNumber decodes numbers into json.Number for interface targets.

type Diagnostic

type Diagnostic = diag.Diagnostic

Diagnostic is the facade alias for exp/diag.Diagnostic.

type DiagnosticContext

type DiagnosticContext struct {
	// Diagnostic is the current diagnostic candidate.
	Diagnostic Diagnostic
	// Index is the 0-based diagnostic index in parse order.
	Index int
	// Depth is the parser recovery nesting depth at this diagnostic.
	Depth int
	// RecoveryEvents is the total number of recovery events observed so far.
	RecoveryEvents int
}

DiagnosticContext describes the current embedded diagnostic.

Example
package main

import (
	"fmt"

	jsonkit "github.com/forgemechanic/jsonkit"
)

func main() {
	ctx := jsonkit.DiagnosticContext{
		Index:          2,
		Depth:          1,
		RecoveryEvents: 4,
	}
	fmt.Println(ctx.Index, ctx.Depth, ctx.RecoveryEvents)
}
Output:

2 1 4

type DiagnosticDecision

type DiagnosticDecision struct {
	// Suppress drops the diagnostic when true.
	Suppress bool
	// Replace substitutes the diagnostic with a new value when non-nil.
	Replace *Diagnostic
	// EndEmbeddedBlock terminates embedded parsing at this point when true.
	EndEmbeddedBlock bool
}

DiagnosticDecision controls embedded diagnostic handling.

Example
package main

import (
	"fmt"

	jsonkit "github.com/forgemechanic/jsonkit"
)

func main() {
	decision := jsonkit.DiagnosticDecision{
		Suppress:         true,
		EndEmbeddedBlock: false,
	}
	fmt.Println(decision.Suppress, decision.EndEmbeddedBlock)
}
Output:

true false

type DiagnosticHook

type DiagnosticHook func(ctx DiagnosticContext) DiagnosticDecision

DiagnosticHook can suppress, replace, or terminate on embedded diagnostics.

type EmbeddedBlock

type EmbeddedBlock struct {
	// Start is the host offset where embedded parsing begins (inclusive).
	Start int64
	// End is the host offset where embedded parsing stops (exclusive).
	End int64
	// Terminators lists byte sequences that can terminate embedded parsing early.
	Terminators [][]byte
	// UseHostOffsets keeps diagnostics/spans in host coordinates when true.
	UseHostOffsets bool
	// DiagnosticHook customizes how embedded diagnostics are handled.
	DiagnosticHook DiagnosticHook
	// HookCodes limits DiagnosticHook invocations to matching diagnostic codes.
	HookCodes []string
}

EmbeddedBlock defines a host range to parse as an embedded JSON fragment.

Example
package main

import (
	"fmt"
	"strings"

	jsonkit "github.com/forgemechanic/jsonkit"
	"github.com/forgemechanic/jsonkit/exp/source"
)

func main() {
	host := []byte("start {\"id\":1} end")
	start := int64(strings.Index(string(host), "{"))
	end := int64(strings.LastIndex(string(host), "}") + 1)

	src := source.NewBytes("host.txt", host)
	res := jsonkit.Parse(src, jsonkit.WithEmbeddedBlock(jsonkit.EmbeddedBlock{
		Start:          start,
		End:            end,
		UseHostOffsets: true,
	}))
	meta, _ := res.Embedded()

	fmt.Println(res.OK())
	fmt.Println(meta.EffectiveStart == start && meta.EffectiveEnd == end)
}
Output:

true
true

type EmbeddedMetadata

type EmbeddedMetadata struct {
	// RequestedStart is the requested embedded start offset from options.
	RequestedStart int64
	// RequestedEnd is the requested embedded end offset from options.
	RequestedEnd int64
	// EffectiveStart is the actual embedded start offset after normalization.
	EffectiveStart int64
	// EffectiveEnd is the actual embedded end offset after normalization.
	EffectiveEnd int64
	// TerminatorHit reports whether parsing stopped due to a terminator.
	TerminatorHit bool
	// TerminatorSpan is the host span of the matched terminator when hit.
	TerminatorSpan Span
}

EmbeddedMetadata reports effective embedded parse bounds.

Example
package main

import (
	"fmt"

	jsonkit "github.com/forgemechanic/jsonkit"
)

func main() {
	meta := jsonkit.EmbeddedMetadata{
		EffectiveStart: 10,
		EffectiveEnd:   20,
	}
	local, okLocal := meta.HostToLocal(15)
	host, okHost := meta.LocalToHost(5)

	fmt.Println(okLocal, local)
	fmt.Println(okHost, host)
}
Output:

true 5
true 15

func (EmbeddedMetadata) EffectiveSpan

func (m EmbeddedMetadata) EffectiveSpan() Span

EffectiveSpan returns the effective embedded span as a Span value.

func (EmbeddedMetadata) HostToLocal

func (m EmbeddedMetadata) HostToLocal(off int64) (int64, bool)

HostToLocal converts a host offset to local embedded offset.

func (EmbeddedMetadata) LocalToHost

func (m EmbeddedMetadata) LocalToHost(off int64) (int64, bool)

LocalToHost converts a local embedded offset to host offset.

type EncodeCommentStyle

type EncodeCommentStyle uint8

EncodeCommentStyle controls emitted comment style.

const (
	// EncodeCommentNone disables comment emission.
	EncodeCommentNone EncodeCommentStyle = iota
	// EncodeCommentLine emits // comments.
	EncodeCommentLine
	// EncodeCommentBlock emits /* */ comments.
	EncodeCommentBlock
)

type EncodeOption

type EncodeOption func(*encodeConfig)

EncodeOption configures MarshalWithOptions and NewEncoderWithOptions.

func WithEncodeEscapeHTML

func WithEncodeEscapeHTML(enabled bool) EncodeOption

WithEncodeEscapeHTML toggles HTML escaping for string output.

func WithEncodeIndent

func WithEncodeIndent(prefix, indent string) EncodeOption

WithEncodeIndent configures pretty-print indentation.

func WithEncodeLeadingComment

func WithEncodeLeadingComment(text string, style EncodeCommentStyle) EncodeOption

WithEncodeLeadingComment prepends a document-level comment.

func WithEncodeMemberComments

func WithEncodeMemberComments(comments map[string]string, style EncodeCommentStyle) EncodeOption

WithEncodeMemberComments injects comments before selected object member keys.

func WithEncodeProfile

func WithEncodeProfile(id ProfileID) EncodeOption

WithEncodeProfile selects the encode profile preset.

func WithEncodeStringQuoteStyle

func WithEncodeStringQuoteStyle(style EncodeQuoteStyle) EncodeOption

WithEncodeStringQuoteStyle selects output string quote style.

func WithEncodeTrailingCommas

func WithEncodeTrailingCommas(enabled bool) EncodeOption

WithEncodeTrailingCommas enables trailing commas in arrays and objects.

func WithEncodeUnquotedKeys

func WithEncodeUnquotedKeys(enabled bool) EncodeOption

WithEncodeUnquotedKeys enables JSON5 unquoted member keys when valid.

func WithEncodeValidateOutput

func WithEncodeValidateOutput(enabled bool) EncodeOption

WithEncodeValidateOutput re-parses encoded output in the selected profile and returns an error if invalid.

type EncodeQuoteStyle

type EncodeQuoteStyle uint8

EncodeQuoteStyle controls output quote style for string literals.

const (
	// EncodeQuoteDouble emits standard JSON double-quoted strings.
	EncodeQuoteDouble EncodeQuoteStyle = iota
	// EncodeQuoteSingle emits JSON5 single-quoted strings where possible.
	EncodeQuoteSingle
	// EncodeQuoteAuto chooses between single and double quoting by escape cost.
	EncodeQuoteAuto
)

type Encoder

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

Encoder provides stream-style encoding similar to encoding/json.Encoder.

Example
package main

import (
	"bytes"
	"fmt"

	jsonkit "github.com/forgemechanic/jsonkit"
)

func main() {
	var buf bytes.Buffer
	enc := jsonkit.NewEncoder(&buf)
	err := enc.Encode(map[string]int{"id": 3})

	fmt.Println(err == nil)
	fmt.Printf("%q\n", buf.String())
}
Output:

true
"{\"id\":3}\n"

func NewEncoder

func NewEncoder(w io.Writer) *Encoder

NewEncoder creates a stream encoder with strict defaults.

func NewEncoderWithOptions

func NewEncoderWithOptions(w io.Writer, opts ...EncodeOption) *Encoder

NewEncoderWithOptions creates a stream encoder with explicit options.

Example
package main

import (
	"bytes"
	"fmt"

	jsonkit "github.com/forgemechanic/jsonkit"
)

func main() {
	var buf bytes.Buffer
	enc := jsonkit.NewEncoderWithOptions(
		&buf,
		jsonkit.WithEncodeProfile(jsonkit.ProfileJSON5),
		jsonkit.WithEncodeUnquotedKeys(true),
		jsonkit.WithEncodeTrailingCommas(true),
	)

	err := enc.Encode(struct {
		ID int `json:"id"`
	}{ID: 2})

	fmt.Println(err == nil)
	fmt.Printf("%q\n", buf.String())
}
Output:

true
"{id:2,}\n"

func (*Encoder) Encode

func (e *Encoder) Encode(v any) error

Encode writes one JSON value and appends a newline.

func (*Encoder) SetEscapeHTML

func (e *Encoder) SetEscapeHTML(on bool)

SetEscapeHTML toggles HTML escaping for stream encoding.

func (*Encoder) SetIndent

func (e *Encoder) SetIndent(prefix, indent string)

SetIndent configures pretty-print indentation for stream encoding.

type Option

type Option func(*config)

Option configures Parse behavior.

func WithEmbeddedBlock

func WithEmbeddedBlock(block EmbeddedBlock) Option

WithEmbeddedBlock restricts Parse to an embedded block definition.

func WithProfile

func WithProfile(id ProfileID) Option

WithProfile selects the parse profile preset.

func WithRetentionMode

func WithRetentionMode(mode RetentionMode) Option

WithRetentionMode selects parse artifact retention level.

type ProfileID

type ProfileID = profile.ProfileID

ProfileID identifies a parse/encode profile preset.

const (
	// ProfileStrict enables strict RFC 8259 JSON behavior.
	ProfileStrict ProfileID = profile.ProfileStrict
	// ProfileJSONC enables JSONC extensions (comments, trailing commas).
	ProfileJSONC ProfileID = profile.ProfileJSONC
	// ProfileJSON5 enables JSON5 extensions.
	ProfileJSON5 ProfileID = profile.ProfileJSON5
)

type Result

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

Result is the facade parse result.

Example
package main

import (
	"fmt"

	jsonkit "github.com/forgemechanic/jsonkit"
	"github.com/forgemechanic/jsonkit/exp/source"
)

func main() {
	src := source.NewBytes("doc.json", []byte(`{"ok":true}`))
	res := jsonkit.Parse(src)

	fmt.Println(res.OK())
	fmt.Println(res.Profile() == jsonkit.ProfileStrict)
}
Output:

true
true

func Parse

func Parse(src Source, opts ...Option) Result

Parse lexes and parses a source document using the selected profile/options.

Parse never panics on language errors. Language issues are returned as diagnostics in the Result. Operational failures (for example, a nil Source) are also surfaced as diagnostics.

Example
package main

import (
	"fmt"

	jsonkit "github.com/forgemechanic/jsonkit"
	"github.com/forgemechanic/jsonkit/exp/source"
)

func main() {
	src := source.NewBytes("example.jsonc", []byte("// note\n{\"id\": 42}\n"))
	res := jsonkit.Parse(
		src,
		jsonkit.WithProfile(jsonkit.ProfileJSONC),
		jsonkit.WithRetentionMode(jsonkit.RetentionValidateOnly),
	)

	fmt.Println(res.OK())
	fmt.Println(res.Mode() == jsonkit.RetentionValidateOnly)
}
Output:

true
true
Example (EmbeddedBlock)
package main

import (
	"fmt"
	"strings"

	jsonkit "github.com/forgemechanic/jsonkit"
	"github.com/forgemechanic/jsonkit/exp/source"
)

func main() {
	host := []byte("before {name:'jsonkit'} after")
	start := int64(strings.Index(string(host), "{"))
	end := int64(strings.LastIndex(string(host), "}") + 1)

	src := source.NewBytes("host.txt", host)
	res := jsonkit.Parse(
		src,
		jsonkit.WithProfile(jsonkit.ProfileJSON5),
		jsonkit.WithEmbeddedBlock(jsonkit.EmbeddedBlock{
			Start:          start,
			End:            end,
			UseHostOffsets: true,
		}),
	)
	meta, ok := res.Embedded()

	fmt.Println(res.OK())
	fmt.Println(ok)
	fmt.Println(meta.EffectiveStart == start && meta.EffectiveEnd == end)
}
Output:

true
true
true
Example (TokensMode)
package main

import (
	"fmt"

	jsonkit "github.com/forgemechanic/jsonkit"
	"github.com/forgemechanic/jsonkit/exp/source"
)

func main() {
	src := source.NewBytes("tokens.json", []byte(`{"id":1}`))
	res := jsonkit.Parse(src, jsonkit.WithRetentionMode(jsonkit.RetentionTokens))

	_, cstOK := res.CST()
	tokens, tokOK := res.TokenStream()

	fmt.Println(cstOK)
	fmt.Println(tokOK && len(tokens) > 0)
}
Output:

false
true

func (Result) CST

func (r Result) CST() (CST, bool)

CST returns a deep-copied concrete syntax tree when full CST retention is enabled.

func (Result) CSTView

func (r Result) CSTView() (CST, bool)

CSTView returns the internal CST view without copying when full CST retention is enabled.

func (Result) Diagnostics

func (r Result) Diagnostics() []Diagnostic

Diagnostics returns a copy of parse diagnostics.

func (Result) Embedded

func (r Result) Embedded() (EmbeddedMetadata, bool)

Embedded returns embedded-range metadata when embedded parsing was active.

func (Result) Mode

func (r Result) Mode() RetentionMode

Mode returns the effective artifact retention mode.

func (Result) OK

func (r Result) OK() bool

OK reports true when no error-severity diagnostics were produced.

func (Result) Profile

func (r Result) Profile() ProfileID

Profile returns the effective profile used for parse.

func (Result) TokenStream

func (r Result) TokenStream() ([]Token, bool)

TokenStream returns a copied token stream when available.

func (Result) TokenStreamView

func (r Result) TokenStreamView() ([]Token, bool)

TokenStreamView returns the internal token stream view when available.

func (Result) Tokens

func (r Result) Tokens() ([]Token, bool)

Tokens returns copied non-trivia tokens when available.

func (Result) TokensView

func (r Result) TokensView() ([]Token, bool)

TokensView returns internal non-trivia tokens without copying.

func (Result) Trivia

func (r Result) Trivia() ([]Token, bool)

Trivia returns copied trivia tokens when available.

func (Result) TriviaView

func (r Result) TriviaView() ([]Token, bool)

TriviaView returns internal trivia tokens without copying.

type RetentionMode

type RetentionMode = expparse.RetentionMode

RetentionMode controls how much parse structure is retained.

const (
	// RetentionFullCST keeps the full concrete syntax tree and token stream.
	RetentionFullCST RetentionMode = expparse.RetentionFullCST
	// RetentionTokens keeps tokens without full CST structure.
	RetentionTokens RetentionMode = expparse.RetentionTokens
	// RetentionStructural keeps structural tokens for fast binding/analysis.
	RetentionStructural RetentionMode = expparse.RetentionStructural
	// RetentionWindowedCST keeps a full CST for the effective parse window.
	// Use with WithEmbeddedBlock to parse/retain only embedded JSON regions.
	RetentionWindowedCST RetentionMode = expparse.RetentionWindowedCST
	// RetentionValidateOnly performs validation with minimal retained artifacts.
	RetentionValidateOnly RetentionMode = expparse.RetentionValidateOnly
)

type Severity

type Severity = diag.Severity

Severity is the facade alias for diagnostic severity.

const (
	// SeverityError marks diagnostics that indicate parse/validation failure.
	SeverityError Severity = diag.SeverityError
	// SeverityWarning marks non-fatal diagnostics.
	SeverityWarning Severity = diag.SeverityWarning
)

type Source

type Source = source.Source

Source is the facade alias for exp/source.Source.

type Span

type Span = span.Span

Span is the facade alias for exp/span.Span.

type Token

type Token = lex.Token

Token is the facade alias for exp/lex.Token.

type TokenKind

type TokenKind = lex.Kind

TokenKind is the facade alias for exp/lex.Kind.

Directories

Path Synopsis
compat
json
Package json provides encoding/json-compatible entry points.
Package json provides encoding/json-compatible entry points.
exp
cst
lex
sem
internal
Package jsonl provides a standalone JSONL API layered on JSONKit primitives.
Package jsonl provides a standalone JSONL API layered on JSONKit primitives.

Jump to

Keyboard shortcuts

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