jfather

package module
v0.0.12 Latest Latest
Warning

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

Go to latest
Published: Aug 4, 2026 License: MIT Imports: 7 Imported by: 61

README

jfather

Parse JSON with line numbers and more!

This is a JSON parsing module that provides additional information during the unmarshalling process, such as line numbers, columns etc.

You can use jfather to unmarshal JSON just like the encoding/json package, and add your own unmarshalling functionality to gather metadata by implementing the jfather.Unmarshaler interface. This requires a single method with the signature UnmarshalJSONWithMetadata(node jfather.Node) error. A full example is below.

You should not use this package unless you need the line/column metadata, as unmarshalling is typically much slower than the encoding/json package:

BenchmarkUnmarshal_JFather-11    	  120945	      9401 ns/op	   14016 B/op	     176 allocs/op
BenchmarkUnmarshal_Traditional-11     326814	      3699 ns/op	    2552 B/op	      56 allocs/op

Full Example

package main

import (
	"fmt"

	"github.com/liamg/jfather"
)

type ExampleParent struct {
	Child ExampleChild `json:"child"`
}

type ExampleChild struct {
	Name   string
	Line   int
	Column int
}

func (t *ExampleChild) UnmarshalJSONWithMetadata(node jfather.Node) error {
	t.Line = node.Range().Start.Line
	t.Column = node.Range().Start.Column
	return node.Decode(&t.Name)
}

func main() {
	input := []byte(`{
	"child": "secret"
}`)
	var parent ExampleParent
	if err := jfather.Unmarshal(input, &parent); err != nil {
		panic(err)
	}

	fmt.Printf("Child value is at line %d, column %d, and is set to '%s'\n",
		parent.Child.Line, parent.Child.Column, parent.Child.Name)

	// outputs:
	//  Child value is at line 2, column 11, and is set to 'secret'
}

Options

By default Unmarshal accepts strict RFC 8259 JSON. You can pass options to accept common non-standard JSON dialects. The dialect options are all opt-in, so the default behaviour is unchanged.

  • AllowComments() — allow // line comments and /* ... */ block comments anywhere whitespace is permitted.
  • AllowTrailingCommas() — allow a single trailing comma before a closing ] or }.
  • AllowUnescapedControlChars() — allow literal control characters (raw newlines, tabs, etc.) inside string values, instead of requiring them to be escaped. Mirrors Python's json.loads(..., strict=False).
  • MaxDepth(n) — override the maximum nesting depth of arrays and objects, which defaults to DefaultMaxDepth (10,000, matching encoding/json). Deeper input is rejected with an error rather than parsed, protecting the parser — and any code that recursively walks the resulting tree — from stack exhaustion on maliciously deep input. The limit cannot be disabled.

These are handy for formats such as JSONC, tsconfig.json, and Azure ARM/Bicep deployment templates, all of which permit comments (and, in ARM's case, long expressions broken across multiple lines inside a single string).

input := []byte(`{
	// the service to deploy
	"name": "example",
	"tags": [ "a", "b", ], /* trailing comma */
}`)

var target map[string]any
if err := jfather.Unmarshal(input, &target,
	jfather.AllowComments(),
	jfather.AllowTrailingCommas(),
); err != nil {
	panic(err)
}

Documentation

Overview

Package jfather is a JSON parser and decoder that provides metadata about the parsed JSON.

Index

Examples

Constants

View Source
const DefaultMaxDepth = 10000

DefaultMaxDepth is the maximum nesting depth of arrays and objects accepted by Unmarshal unless overridden with the MaxDepth option. The parser (and any code that later walks the resulting tree) recurses once per nesting level, so depth must be bounded to prevent maliciously deep input from overflowing the stack. The value matches encoding/json's nesting limit.

Variables

This section is empty.

Functions

func Unmarshal

func Unmarshal(data []byte, target any, opts ...Option) error

Unmarshal unmarshals the given JSON data into the target value. It will attempt to use UnmarshalJSONWithMetadata if the target implements it, otherwise it will use the default json unmarshaler.

By default the input must be strict JSON. Pass options such as AllowComments or AllowTrailingCommas to accept common JSON dialects.

Types

type Kind

type Kind uint8

Kind represents the type of a node.

const (
	// KindUnknown represents an unknown kind.
	KindUnknown Kind = iota
	// KindNull represents a null value.
	KindNull
	// KindNumber represents a number value.
	KindNumber
	// KindString represents a string value.
	KindString
	// KindBoolean represents a boolean value.
	KindBoolean
	// KindArray represents an array value.
	KindArray
	// KindObject represents an object value.
	KindObject
)

type Node

type Node interface {
	Range() Range
	Decode(target any) error
	Kind() Kind
	Content() []Node
}

Node represents a node in the AST.

type Option added in v0.0.10

type Option func(*parser)

Option configures optional parsing behaviour. The dialect options (AllowComments, AllowTrailingCommas, AllowUnescapedControlChars) are all off by default, so Unmarshal stays strict JSON unless a caller opts in.

func AllowComments added in v0.0.10

func AllowComments() Option

AllowComments permits JavaScript-style comments — // to end of line and /* ... */ block comments — anywhere whitespace is allowed. Standard JSON forbids comments, but several dialects permit them (JSONC, tsconfig, and ARM/Bicep deployment templates).

Example
package main

import (
	"fmt"

	"github.com/liamg/jfather"
)

func main() {
	input := []byte(`{
	// the service to deploy
	"name": "example" /* block comment */
}`)

	var target struct {
		Name string `json:"name"`
	}
	if err := jfather.Unmarshal(input, &target, jfather.AllowComments()); err != nil {
		panic(err)
	}

	fmt.Println(target.Name)
}
Output:
example

func AllowTrailingCommas added in v0.0.10

func AllowTrailingCommas() Option

AllowTrailingCommas permits a single trailing comma before a closing ']' or '}'. Standard JSON forbids it.

Example
package main

import (
	"fmt"

	"github.com/liamg/jfather"
)

func main() {
	input := []byte(`{ "tags": [ "a", "b", ], }`)

	var target struct {
		Tags []string `json:"tags"`
	}
	if err := jfather.Unmarshal(input, &target, jfather.AllowTrailingCommas()); err != nil {
		panic(err)
	}

	fmt.Println(target.Tags)
}
Output:
[a b]

func AllowUnescapedControlChars added in v0.0.11

func AllowUnescapedControlChars() Option

AllowUnescapedControlChars permits literal control characters (U+0000 through U+001F), such as raw newlines and tabs, to appear inside string values. Standard JSON requires these to be escaped, but some producers emit them literally — for example Azure ARM/Bicep templates routinely break a long expression across several lines inside a single string value. This mirrors Python's json.loads(..., strict=False).

Example
package main

import (
	"fmt"

	"github.com/liamg/jfather"
)

func main() {
	// A raw newline inside the string value (an ARM-style expression
	// broken across lines) would fail strict JSON.
	input := []byte("{ \"expr\": \"[concat('a',\n'b')]\" }")

	var target struct {
		Expr string `json:"expr"`
	}
	if err := jfather.Unmarshal(input, &target, jfather.AllowUnescapedControlChars()); err != nil {
		panic(err)
	}

	fmt.Printf("%q\n", target.Expr)
}
Output:
"[concat('a',\n'b')]"

func MaxDepth added in v0.0.12

func MaxDepth(depth int) Option

MaxDepth overrides the maximum nesting depth of arrays and objects, which defaults to DefaultMaxDepth. Input nested deeper than the limit is rejected with an error rather than parsed, protecting the parser — and callers that recursively walk the resulting tree — from stack exhaustion on maliciously deep input. Values less than 1 are ignored, leaving the default in place; the limit cannot be disabled.

type PeekReader

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

PeekReader is a reader that allows peeking at the next rune.

func NewPeekReader

func NewPeekReader(reader io.Reader) *PeekReader

NewPeekReader returns a new PeekReader.

func (*PeekReader) Next

func (r *PeekReader) Next() (rune, error)

Next returns the next rune.

func (*PeekReader) Peek

func (r *PeekReader) Peek() (rune, error)

Peek returns the next rune without advancing the reader.

func (*PeekReader) Undo

func (r *PeekReader) Undo() error

Undo undoes the last Next call.

type Position

type Position struct {
	Line   int
	Column int
}

Position represents a position in the source code. Note that both lines and columns are 1-indexed.

type Range

type Range struct {
	Start Position
	End   Position
}

Range represents a range of positions in the source.

type Unmarshaler added in v0.0.8

type Unmarshaler interface {
	UnmarshalJSONWithMetadata(node Node) error
}

Unmarshaler is an interface that can be implemented by types that can unmarshal a JSON description of themselves.

Directories

Path Synopsis
_examples
basic command

Jump to

Keyboard shortcuts

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