jsonr

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Apr 7, 2026 License: MIT Imports: 10 Imported by: 0

README

jsonr

jsonr unmarshals JSON into Go values using dot-notation paths on struct tags, while streaming tokens from encoding/json/jsontext. You describe only the fields you need; everything else can be skipped without loading the whole document into memory.

Status

jsonr is in active development. Public APIs and behavior may change without notice; there is no commitment to stability or backward compatibility yet. The software is provided as-is, without warranties or guarantees — use it only after your own evaluation.

Requirements

  • Go 1.26.1 or later (see go.mod)

Install

go get github.com/exnihilocode/jsonr

Use the import path from your published go.mod (for example github.com/you/jsonr). This repository currently declares module jsonr; replace <module/path> accordingly when you release.

Quick start

import (
	"log"
	"strings"

	"jsonr"
)

var v struct {
	Name      string  `jsonr:"name"`
	Latitude  float64 `jsonr:"address.coordinates.latitude"`
	Longitude float64 `jsonr:"address.coordinates.longitude"`
}
input := `{"name":"North","address":{"coordinates":{"latitude":12.5,"longitude":-3.0}},"noise":{"x":1}}`
if err := jsonr.Unmarshal(strings.NewReader(input), &v); err != nil {
	log.Fatal(err)
}
// v.Name == "North", v.Latitude, v.Longitude set; "noise" was skipped at token scope

API

  • Unmarshal(r io.Reader, v any) error — decode into v from a reader. v must be a non-nil pointer to a struct, map, slice, or array. For structs, only exported fields with a jsonr tag are filled; other fields are left untouched.

  • UnmarshalDecode(dec *jsontext.Decoder, v any) error — same as Unmarshal, but you supply a jsontext.Decoder. Use this when you already have a decoder or want to read multiple top-level JSON values from one stream.

  • Inline[T any](r io.Reader, path string) (T, error) — read one value at path from the reader. The next token must start a JSON object or array. Path syntax matches struct tags (*, numeric indices, and a terminal y.[a,b] multi-field segment). Wildcards require a slice destination; bracket segments require map[string]T. Struct destinations are rejected except [time.Time]. On success, that value is consumed; trailing siblings stay in the stream.

  • InlineDecode[T any](dec *jsontext.Decoder, path string) (T, error) — same as Inline, but reads from an existing jsontext.Decoder at the start of an object or array.

Time values (time.Time)

time.Time fields (and slices or pointers of them) decode from JSON strings, numbers, or null. null yields the zero time, or a nil *time.Time.

A JSON number is treated as Unix time in seconds (UTC), same as time.Unix. The decoder uses the number’s integer value; any fractional part is truncated toward zero (matching jsontext.Token.Int).

Accepted string forms include RFC3339 / RFC3339Nano (typical ISO 8601 with offset or Z), date-time without zone in UTC (YYYY-MM-DDTHH:MM:SS or space instead of T), date only YYYY-MM-DD at 00:00 UTC, and time only HH:MM:SS or HH:MM in UTC (calendar date parts are the zero date; see package time). Surrounding ASCII space is trimmed. A numeric value sent as a quoted string is parsed with the string rules above, not as Unix seconds.

Path syntax (jsonr struct tags)

Form Meaning
a.b.c Walk nested objects by key.
items.0.id Index into a JSON array (0 is the first element).
people.*.name For each array element, resolve name; append to a slice field (elements without the path are skipped).
obj.[a,b] Read multiple keys from one object into map[string]T. Not supported for slice/array destinations. Brackets list key names only, not ranges like [0:5].

Inline supports the same path forms as the table above in one path string; multi-field brackets must be the last segment (for example x.y.*.[a,b] into []map[string]T).

Examples

Wildcard into a slice
import (
	"strings"

	"jsonr"
)

var v struct {
	Names []string `jsonr:"people.*.name"`
}
input := `{"people":[{"name":"a"},{"skip":true},{"name":"b"}]}`
_ = jsonr.Unmarshal(strings.NewReader(input), &v)
// v.Names == []string{"a","b"}
Multi-field into a map
import (
	"strings"

	"jsonr"
)

var v struct {
	X int             `jsonr:"x"`
	Y map[string]int  `jsonr:"y.[a,b]"`
}
input := `{"x":1,"y":{"a":2,"b":3,"c":4}}`
_ = jsonr.Unmarshal(strings.NewReader(input), &v)
// v.Y has keys "a" and "b" only
Inline decode from a decoder
import (
	"encoding/json/jsontext"
	"log"
	"strings"

	"jsonr"
)

dec := jsontext.NewDecoder(strings.NewReader(`{"address":{"coordinates":{"latitude":1.5,"longitude":2.5}}}`))
lat, err := jsonr.InlineDecode[float64](dec, "address.coordinates.latitude")
if err != nil {
	log.Fatal(err)
}
// dec is now positioned after the latitude value; you could InlineDecode another field or continue manually
_ = lat
Inline wildcard and multi-field
dec := jsontext.NewDecoder(strings.NewReader(`{"x":{"y":[{"a":1,"c":9},{"a":2,"b":3}]}}`))
rows, err := jsonr.InlineDecode[[]map[string]int](dec, "x.y.*.[a,b]")
if err != nil {
	log.Fatal(err)
}
// rows == []map[string]int{{"a":1}, {"a":2,"b":3}} — first object has no "b", so that map omits it
_ = rows

Design notes

  • Streaming: Matching paths are decoded; other subtrees are skipped via the decoder without materializing them as large Go values.
  • Structs: Tag-only selection—there is no fallback to encoding/json field names for untagged struct fields in this mode.
  • Not supported: Slice-range syntax in paths; multi-field tags into []T / [N]T (use map[string]T for multi-key object picks).

For more tag examples, see docs/path_examples.md. Some examples there describe roadmap or alternate shapes; bracket syntax is keys only, and multi-field tags require a map destination in the current implementation.

License

This project is released under the MIT License.

Documentation

Run go doc -all in this package or see doc.go for the full package overview.

Documentation

Overview

Package jsonr streams JSON and decodes only the values described by dot-notation paths in struct tags ("jsonr"), without buffering entire documents. Uninterested subtrees are skipped at the token level using encoding/json/jsontext.

Unmarshal

Unmarshal fills exported fields that carry a jsonr tag. The destination must be a non-nil pointer to a struct, map, slice, or fixed-size array. For a struct, the document must be a top-level JSON object or null. For a map, the document must be an object. For a slice or array, the document must be a JSON array. Fields without a jsonr tag are ignored when unmarshaling into a struct.

Path syntax (jsonr struct tags)

  • Dot-separated keys traverse nested objects (for example address.coordinates.latitude).

  • A numeric segment selects an array element (for example items.0.id).

  • An asterisk walks every array element and collects values; the destination field must be a slice (or pointer to slice). Elements missing the rest of the path are skipped with no placeholder entry.

  • A bracket segment lists multiple keys under one object (for example obj.[a,b]); the tag expands to several paths, so the destination field must be map[string]T with string keys. Slice and array destinations are not supported for multi-field tags. Bracket syntax names keys only, not slice ranges like [0:5].

Time values

Fields of type time.Time decode from a JSON string, a JSON number, or null. JSON null sets the field to the zero time. For time pointer fields, null sets the pointer to nil.

A JSON number is interpreted as **Unix time in seconds** (see time.Unix) and decodes to UTC. The numeric token’s integer value is used; any fractional part is truncated toward zero (consistent with encoding/json/jsontext.Token.Int). JSON strings use the following layouts, tried in order: RFC3339 with optional fractional seconds, RFC3339, date-time without a zone in UTC (layouts "2006-01-02T15:04:05" and "2006-01-02 15:04:05"), date-only "2006-01-02" at midnight UTC, then time-only "15:04:05" or "15:04" in UTC. For time-only strings, missing calendar fields use the zero date in UTC (as with time.ParseInLocation in UTC). Leading and trailing ASCII space is trimmed before parsing strings. Quoted numeric strings are parsed as strings, not as Unix seconds.

Inline

Inline decodes one JSON value selected by path from the reader's current position. Path uses the same dot notation as jsonr struct tags, including wildcard segments ("*") and multi-field bracket segments that must appear last (e.g. "y.[a,b]"). Wildcard paths require a slice destination; multi-field paths require map[string]T. Elements missing the rest of the path under a wildcard are skipped with no placeholder.

The destination type must not be a struct other than time.Time. The document root must be a JSON object or array. After a successful call, the reader is advanced past the decoded value. Remaining tokens in the document (sibling keys, trailing values) are left unconsumed.

InlineDecode

InlineDecode decodes a single value from a encoding/json/jsontext.Decoder already positioned at the start of a JSON object or array. See the Inline section for path syntax and constraints. After success the decoder has consumed the selected value; remaining input is left for the caller.

Relationship to encoding/json

jsonr is a companion to encoding/json: it uses the standard library's JSON token decoder for streaming and does not replace full Unmarshal of arbitrary untagged types.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Inline

func Inline[T any](r io.Reader, path string) (T, error)

Inline decodes one JSON value selected by path from the reader's current position. Path uses the same dot notation as jsonr struct tags, including wildcard segments ("*") and multi-field bracket segments that must appear last (e.g. "y.[a,b]"). Wildcard paths collect one value per array element or object entry; when the rest of the path is missing for an element, that element is skipped with no placeholder. Multi-field paths decode only the listed keys into a map[string]T.

The destination type T must not be a struct other than time.Time. Maps must use string keys.

The document root must be a JSON object or array. After a successful call, the reader is advanced past the decoded value. Remaining tokens in the document (sibling keys, trailing values) are left unconsumed.

func InlineDecode

func InlineDecode[T any](dec *jsontext.Decoder, path string) (T, error)

InlineDecode decodes one JSON value selected by path from the decoder's current position. See Inline for path syntax and wildcard / multi-field behavior.

The decoder must be positioned at the start of a JSON object or array. After success the decoder has consumed the selected value; remaining input is left for the caller.

func Unmarshal

func Unmarshal(r io.Reader, v any) error

Unmarshal streams JSON from r and fills only the exported fields of v that carry a jsonr tag. v must be a non-nil pointer to a struct, map, slice, or array. For a struct, the JSON document must be an object at the top level (or null, in which case nothing is written). For a map, the JSON must be an object; for a slice or array, a JSON array.

func UnmarshalDecode

func UnmarshalDecode(dec *jsontext.Decoder, v any) error

UnmarshalDecode is like Unmarshal but takes a jsontext.Decoder instead of an io.Reader. This is useful for streaming multiple top-level values from a single JSON document.

Types

This section is empty.

Jump to

Keyboard shortcuts

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