jsonstat

package module
v0.1.1 Latest Latest
Warning

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

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

README

jsonstat-go

A Go-native library for the JSON-stat 2.0 format — decode, traverse, transform and encode statistical cubes with a small, idiomatic API that depends only on the Go standard library.

Features

  • Decode any 2.0 response class (dataset, collection, dimension) and the pre-2.0 bundle container, with transparent normalisation of the polymorphic parts of the format:
    • dense ("value": [1, 2, null, …]) and sparse ("value": {"0": 1, "2": 3}) value forms,
    • all three status forms (single string, array, sparse object).
  • Traverse cubes by flat row-major index, by per-dimension category coordinates, by map[dimID]catID, or lazily with a Go 1.23 iter.Seq2 iterator.
  • Transform a cube into four tabular shapes (array of arrays, array of objects, columnar object, Google DataTable) with pivoting, dropping, status, and metadata options.
  • Unflatten a cube with a callback that receives each cell's coordinates and value/status — the building block the higher-level transform is built on.
  • Encode a model back to canonical JSON-stat bytes with stable property ordering and round-trip fidelity on the bundled fixtures.
  • Typed errors with structured context (operation, dimension id, category index, flat index) recoverable through errors.As.

Installation

go get github.com/jsonstat/go

The import path is github.com/jsonstat/go. The module targets Go 1.23 (for the range-over-func iterator used by Dataset.Cells).

Decode

Decode reads from any io.Reader; DecodeBytes is the byte-slice shortcut. A document holds zero, one or many datasets — use Document.SingleDataset for the common case or Document.Datasets for collections and pre-2.0 bundles.

package main

import (
    "fmt"
    "os"

    "github.com/jsonstat/go"
)

func main() {
    f, err := os.Open("oecd.json")
    if err != nil {
        panic(err)
    }
    defer f.Close()

    doc, err := jsonstat.Decode(f)
    if err != nil {
        panic(err)
    }

    ds, err := doc.SingleDataset()
    if err != nil {
        panic(err)
    }

    fmt.Println(ds.Label)                  // "Unemployment rate in the OECD countries 2003-2014"
    fmt.Println(ds.DimensionIDs())         // [concept area year]
    fmt.Println(ds.SizeSlice())            // [1 36 12]
    fmt.Println("cells:", ds.N())          // cells: 432
}

Traverse

Cells are addressed three ways, all returning a Cell that carries its coordinates, value and status:

// By flat row-major index ("what does not change, first"; last dimension
// varies fastest).
c, err := ds.CellByFlat(0)

// By per-dimension category coordinates (one int per dimension, in ID order).
c, err = ds.CellByCoord(0, 0, 0)

// By dimension-ID -> category-ID map.
c, err = ds.CellByLabel(map[string]string{
    "concept": "UNR",
    "area":    "AU",
    "year":    "2003",
})

fmt.Printf("value=%v status=%v coords=%v missing=%t\n",
    c.Value, c.Status, c.Coords, c.Missing)

For bulk iteration prefer the lazy Cells iterator, which allocates no per-cell slice until the callback is reached:

for c, err := range ds.Cells() {
    if err != nil {
        return err
    }
    if c.Missing {
        continue
    }
    total += c.Value
}

Walk is the callback equivalent for pre-1.23 call sites, and DimensionsByRole lets you pick dimensions by JSON-stat role (time, geo, metric):

time, _ := ds.DimensionByRole(jsonstat.RoleTime)
fmt.Println(time.ID, time.Size) // year 12

Transform

Dataset.Transform converts a cube into one of four tabular shapes, configured with functional options:

// 1. Array of arrays (the default). The first row is the header; its column
//    names follow the `field` setting, while category values follow `content`.
//    The array type defaults to field=label, so pass WithField(FieldID) for
//    ID-style column names; pair it with WithContent(ContentID) to also render
//    category IDs instead of labels.
rows, _ := ds.Transform(
    jsonstat.WithStatus(true),
    jsonstat.WithField(jsonstat.FieldID),
    jsonstat.WithContent(jsonstat.ContentID),
)
header := rows.([][]any)[0]
// ["concept", "area", "year", "status", "value"]
dataRow := rows.([][]any)[1]
// ["UNR", "AU", "2003", "", 5.943826289]

// 2. Array of objects (one map per cell). Optional `by` pivots a dimension's
//    categories into columns; `drop` removes single-category dimensions;
//    `prefix` namespaces pivoted column keys.
out, _ := ds.Transform(
    jsonstat.WithTransformType(jsonstat.TransformArrObj),
    jsonstat.WithBy("year"),
    jsonstat.WithDrop("concept"),
    jsonstat.WithPrefix("y_"),
)
records := out.([]map[string]any)

// 3. Columnar object (one parallel array per column).
out, _ = ds.Transform(
    jsonstat.WithTransformType(jsonstat.TransformObjArr),
    jsonstat.WithStatus(true),
)
cols := out.(map[string][]any)
// cols["area"]  == ["AU", "AU", …]
// cols["value"] == [5.943826289, 5.39663128, …]

// 4. Google DataTable ({cols, rows}), suitable for Google Charts.
out, _ = ds.Transform(jsonstat.WithTransformType(jsonstat.TransformObject))
dt := out.(*jsonstat.DataTable)

When meta is true the result is wrapped in a TableWithMeta object carrying the dataset's label, source, updated timestamp, the applied options, and per-dimension category metadata:

out, _ := ds.Transform(
    jsonstat.WithTransformType(jsonstat.TransformArrObj),
    jsonstat.WithStatus(true),
    jsonstat.WithBy("sex"),
    jsonstat.WithDrop("country", "year"),
    jsonstat.WithMeta(true),
)
wrapped := out.(*jsonstat.TableWithMeta)
fmt.Println(wrapped.Meta.Type, wrapped.Meta.By, wrapped.Meta.Drop)

The available options are:

Option Purpose
[WithTransformType] Output shape: TransformArray (default), TransformArrObj, TransformObjArr, TransformObject.
[WithStatus] Include the status column. Ignored when WithBy is in effect.
[WithContent] Category values are IDs (ContentID) or labels (ContentLabel, default).
[WithField] Column keys are IDs (FieldID, default) or labels (FieldLabel; the array type defaults to label).
[WithValueLabel] Rename the value column (default "Value").
[WithStatusLabel] Rename the status column (default "Status).
[WithMeta] Wrap the result in TableWithMeta. Not valid with TransformObject.
[WithBy] Pivot the named dimension's categories into columns. arrobj/objarr only.
[WithPrefix] Prefix for pivoted column keys. Only honoured when WithBy is active.
[WithDrop] Omit single-category dimensions from the output.
[WithComma] Render numeric values as comma-decimal strings. Not valid with TransformObject.

Unflatten

For anything the option-based transform can't express, Dataset.Unflatten hands every cell to a callback with its coordinates, value/status pair and the row being built. Returning nil drops the cell from the output.

// Collect only cells whose value exceeds a threshold, projecting each to a
// custom record type.
type peak struct {
    Area string  `json:"area"`
    Year string  `json:"year"`
    Rate float64 `json:"rate"`
}

rows, err := ds.Unflatten(func(coords jsonstat.Coordinates, dp jsonstat.Datapoint, n int, row []any) any {
    if dp.Missing || dp.Value < 10 {
        return nil
    }
    return peak{Area: coords["area"], Year: coords["year"], Rate: dp.Value}
})
// rows is []any; each non-nil element is a peak.

Encode

MarshalDataset, MarshalDocument and Encode (which writes to a *bytes.Buffer) emit canonical JSON-stat bytes with stable property ordering. Dataset.MarshalJSON and Document.MarshalJSON make the types usable directly from encoding/json:

// Round-trip: decode -> mutate -> re-encode.
doc, _ := jsonstat.DecodeBytes(src)
ds, _ := doc.SingleDataset()
// …traverse or transform ds…

out, err := jsonstat.MarshalDocument(doc)
if err != nil {
    panic(err)
}
_ = os.WriteFile("oecd.out.json", out, 0o644)

Fixtures

The testdata/ directory bundles the canonical JSON-stat sample files (canada.json, oecd.json, order.json, sparse.json, status-array.json, us-unr.json, …) used as round-trip fixtures. They are the easiest way to explore the model:

doc, _ := jsonstat.DecodeBytes(mustRead("testdata/canada.json"))
ds, _  := doc.SingleDataset()
// ds.DimensionIDs() == [country year age concept sex]
// ds.SizeSlice()   == [1 1 20 2 3]
// ds.N()           == 120

License

Apache License 2.0.

Documentation

Overview

Package jsonstat is a Go-native library for the JSON-stat 2.0 format.

jsonstat-go is a complete round-trip alternative to the JavaScript JSON-stat Toolkit + Utilities Suite, targeting backend and cloud-native use cases (Go microservices, statistical agencies, open-data platforms). It is the first JSON-stat library written in Go and intentionally drops the legacy parts of the JS Toolkit: there is no toTable and no Slice. The non-deprecated tabular methods are Dataset.Unflatten (callback-based, faithful port of the JS Toolkit method of the same name) and Dataset.Transform (options-based, faithful port of the JS Toolkit method of the same name); the single subsetting method is Dataset.Dice.

JSON-stat in one paragraph

A JSON-stat dataset is a multi-dimensional cube. Cells sit at the intersection of dimensions; the flat [Dataset.value] array lists cells in row-major order ("what does not change, first"). The last dimension in [Dataset.id] changes fastest. See the package examples for the stride math that converts between flat indices and dimension coordinates.

Three surfaces, one model

Every operation in jsonstat-go is a function over the same in-memory model:

  • Client: decode, traverse, subset (Dice), transform (Unflatten, Transform), encode.
  • Authoring: build cubes programmatically with the fluent Builder.
  • Validate: structural + semantic tiers, pure Go (see Validate).

The companion subpackage github.com/jsonstat/go/jsonstathttp adds an net/http handler that serves a Dataset with query-string subsetting and content negotiation (JSON-stat, CSV-stat, JSON, CSV).

Decode a document

jsonstat.Decode(r io.Reader) (*Dataset, error)
jsonstat.Fetch(ctx context.Context, url string, opts ...FetchOption) (*Dataset, error)

Decode accepts both the dense ("value": [1, 2, null, ...]) and sparse ("value": {"0": 1, "2": 3}) value forms, and all three status forms (array, single string, object). The runtime representation normalises them so callers never branch on the wire format.

Traverse

ds.Dimension(id string) (*Dimension, error)
ds.DimensionByRole(role Role) (*Dimension, error)
ds.CellByFlat(i int) (Cell, error)              // flat row-major index
ds.CellByCoord(coords ...int) (Cell, error)     // one category index per dim
ds.CellByLabel(map[string]string) (Cell, error) // map[dimID]catID
ds.Cells() iter.Seq2[Cell, error]               // lazy, Go 1.23 range-over-func
ds.Walk(func(Cell) error)                        // callback iteration

Transform and subset

ds.Unflatten(fn UnflattenFunc) ([]any, error)                       // raw cell stream (faithful JS port)
ds.Transform(opts ...TransformOption) (any, error)                  // array / arrobj / objarr / object (faithful JS port)
ds.Dice(jsonstat.Filter, opts ...DiceOption) (*Dataset, error)      // no Slice

Idiomatic Go, not a port

The JS Toolkit uses polymorphic methods (null returns, overloaded argument shapes, boolean flags). jsonstat-go uses typed returns plus an error, the functional-options pattern for configuration, context.Context for cancellation, log/slog for structured logging, and iter.Seq2 for lazy iteration. It depends only on the Go standard library at runtime.

Status

The current release is v0.1.1. The compiled-in Version reads "0.1.1" and is overridable at link time; release builds inject the real version from VCS via -ldflags (see Version). See FormatVersion for the JSON-stat wire-format version implemented here.

Index

Constants

View Source
const (
	ClassDataset    = "dataset"
	ClassCollection = "collection"
	ClassDimension  = "dimension"
	// ClassBundle is the legacy pre-2.0 container. It is not a valid 2.0
	// class but is recognised by [Decode] for backwards compatibility.
	ClassBundle = "bundle"
)

ClassDataset, ClassCollection, ClassDimension are the JSON-stat 2.0 response class values. The pre-2.0 bundle has no class value; documents without a class are treated as ClassDataset by tolerant decoders (see Decode).

View Source
const (
	PositionStart = "start"
	PositionEnd   = "end"
)

PositionStart and PositionEnd are the two legal values for Unit.Position.

View Source
const FormatVersion = "2.0"

FormatVersion is the JSON-stat format version implemented by this library.

jsonstat-go targets JSON-stat 2.0, matching the rest of the modern ecosystem (toolkit, php, io, validator, wasm). Pre-2.0 bundle documents are tolerated on decode (see Decode) but never produced on encode.

Variables

View Source
var ErrBuildIncomplete = errors.New("jsonstat: incomplete cube definition")

ErrBuildIncomplete is returned by Builder.Build when the cube is missing required pieces (dimensions, categories, or values).

View Source
var ErrCategoryNotFound = errors.New("jsonstat: category not found")

ErrCategoryNotFound is returned when a requested category ID is not present in a dimension.

View Source
var ErrCellMissing = errors.New("jsonstat: cell has no value")

CellMissing reports that the requested cell has no value (a JSON null in a dense value array, or an absent key in a sparse value object). It is not an error: callers should use Cell.Missing to test rather than errors.Is, but the sentinel is exported so that error-returning helpers can wrap it.

View Source
var ErrCoordLenMismatch = errors.New("jsonstat: coordinate length mismatch")

ErrCoordLenMismatch is returned when a coordinate vector length does not match the dataset's dimension count.

View Source
var ErrDatasetNotFound = errors.New("jsonstat: dataset not found")

ErrDatasetNotFound is returned when a requested dataset is not present in the document (for example, doc.Dataset(99) on a single-dataset document).

View Source
var ErrDimensionNotFound = errors.New("jsonstat: dimension not found")

ErrDimensionNotFound is returned when a requested dimension ID is not declared in the dataset.

View Source
var ErrEmptyDocument = errors.New("jsonstat: empty document")

ErrEmptyDocument is returned by decode when the input is empty or only whitespace.

View Source
var ErrHTTPFailure = errors.New("jsonstat: HTTP request failed")

ErrHTTPFailure is returned by Fetch when the server returns a non-2xx status code. The wrapping ValueError carries the status code in the Flat field.

View Source
var ErrIndexOutOfRange = errors.New("jsonstat: index out of range")

ErrIndexOutOfRange is returned when a numeric index (dataset index, category index, or cell coordinate) is out of range.

View Source
var ErrInvalidSize = errors.New("jsonstat: invalid size")

ErrInvalidSize is returned by decode/validate when the size array is inconsistent (negative entries, length mismatch with id, etc.).

View Source
var ErrItemNotFound = errors.New("jsonstat: item not found")

ErrItemNotFound is returned when a collection item lookup fails.

View Source
var ErrMissingRequired = errors.New("jsonstat: missing required property")

ErrMissingRequired is returned by validate when a JSON-stat 2.0 required property is absent.

View Source
var ErrResponseTooLarge = errors.New("jsonstat: response too large")

ErrResponseTooLarge is returned by Fetch when the response body exceeds the limit set by WithMaxBytes.

View Source
var ErrSparseValueLength = errors.New("jsonstat: sparse value key out of range")

ErrSparseValueLength is returned by validate when a sparse value object contains a key ≥ the dataset's total cell count.

View Source
var ErrStatusShape = errors.New("jsonstat: invalid status shape")

ErrStatusShape is returned by decode when the "status" property is neither a string, an array, nor an object.

View Source
var ErrUnknownClass = errors.New("jsonstat: unknown response class")

ErrUnknownClass is returned by decode when the document's "class" is not one of the JSON-stat 2.0 response classes.

View Source
var ErrUnsupportedVersion = errors.New("jsonstat: unsupported version")

ErrUnsupportedVersion is returned by decode when the document's "version" is below 2.0 and the document does not appear to be a tolerated pre-2.0 bundle. Pre-2.0 bundles are accepted (see Decode) but never re-emitted by Encode.

View Source
var ErrValueShape = errors.New("jsonstat: invalid value shape")

ErrValueShape is returned by decode when the "value" property is neither an array nor an object, or by traverse when the resolved flat index is out of range for the value store.

View Source
var Version = "0.1.1"

Version is the semantic version of the jsonstat-go library.

It is a var rather than a const so that release builds can inject the real version from VCS at link time, for example:

go build -ldflags '-X github.com/jsonstat/go.Version=v0.1.1' ./cmd/jsonstat

or, equivalently, via git:

go build -ldflags "-X github.com/jsonstat/go.Version=$(git describe --tags --always --dirty)" ./cmd/jsonstat

When no -ldflags override is supplied, the default below is used. The CLI's Makefile `install-cli` target injects the version automatically when a git working tree is present; see the "Status" section of API.md.

Note that Go modules do not expose build/debug.ReadBuildInfo() version metadata for the main module itself (only for dependencies), so a compile-time value is required for runtime introspection of the library version.

Functions

func CoordinatesCategoryKey

func CoordinatesCategoryKey(prefix string, catIndex int) string

CoordinatesCategoryKey returns the string key under which a category's coordinates would be stored in the source JSON-stat object. Exported as a small helper for tests that build synthetic fixtures programmatically.

func Encode

func Encode(w *bytes.Buffer, doc *Document) error

Encode serialises doc to its JSON-stat 2.0 wire form and writes it to w. The output is compact (no extraneous whitespace) and uses canonical JSON-stat property ordering.

Encode supports single-dataset, single-collection, and single-dimension documents. Pre-2.0 bundles are rejected: marshal each entry of Document.Bundle individually instead.

func EncodeBytes

func EncodeBytes(doc *Document) ([]byte, error)

EncodeBytes is like Encode but returns the marshalled bytes.

func FormatValidationReport

func FormatValidationReport(errs []ValidationError) string

FormatValidationReport renders a slice of validation errors as a human-readable, path-sorted multi-line string. Useful for CLI tools and test failure output.

func MarshalCollection

func MarshalCollection(c *Collection) ([]byte, error)

MarshalCollection returns the JSON-stat wire form of a collection.

func MarshalDataset

func MarshalDataset(ds *Dataset) ([]byte, error)

MarshalDataset returns the JSON-stat wire form of a single dataset.

func MarshalDimension

func MarshalDimension(dim *Dimension) ([]byte, error)

MarshalDimension returns the JSON-stat wire form of a standalone dimension response (class "dimension").

func MarshalDocument

func MarshalDocument(doc *Document) ([]byte, error)

MarshalDocument marshals a Document to its JSON-stat wire form. It is the functional inverse of DecodeBytes.

func VersionInfo

func VersionInfo() map[string]string

VersionInfo returns build metadata about the library and the JSON-stat format version it implements. It is intended for diagnostics, /version endpoints, and CLI banners.

Types

type Builder

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

Builder is a fluent constructor for Dataset instances. It is the Go-native authoring counterpart of Decode: instead of parsing JSON-stat bytes, a caller assembles a cube dimension by dimension from Go data, then calls Builder.Build to materialise a ready-to-use Dataset.

The intended usage reads top-to-bottom:

ds, err := jsonstat.NewBuilder().
    Label("Population by sex and year").
    Dim("sex", "Sex").Cat("F", "Female").Cat("M", "Male").End().
    Dim("year", "Year").Cat("2020", "").Cat("2021", "").End().
    Values([]float64{100, 110, 200, 210}). // [F2020, F2021, M2020, M2021]
    Build()

The values slice is interpreted in JSON-stat row-major order — the last declared dimension varies fastest — exactly as if it had been decoded from a dense "value" array. Use Builder.SparseValues for a sparse (object) value form; the resulting Dataset encodes back to the same form.

func NewBuilder

func NewBuilder() *Builder

NewBuilder returns an empty Builder ready for Dim/Cat/Values/Status calls.

func (*Builder) Build

func (b *Builder) Build() (*Dataset, error)

Build materialises the configured cube into a Dataset. It validates that at least one dimension is declared, that every dimension has at least one category, that the value array (when dense) has the expected length, and that sparse value keys fit within the cube. On success the returned Dataset is fully reindexed and ready for traversal, transform, or encode.

The Builder is not mutated by Build and may be reused to build further cubes (each call produces a fresh Dataset).

func (*Builder) DenseValuesWithMissing

func (b *Builder) DenseValuesWithMissing(vals []float64, missing []bool) *Builder

DenseValuesWithMissing is like Builder.Values but accepts a parallel "missing" slice whose true entries mark absent observations (encoded as JSON null in dense form).

func (*Builder) Dim

func (b *Builder) Dim(id, label string) *DimBuilder

Dim begins a new dimension declaration. Subsequent DimBuilder.Cat calls add categories; DimBuilder.End closes the dimension and returns the parent Builder.

func (*Builder) Extension

func (b *Builder) Extension(key string, value any) *Builder

Extension attaches a provider-specific extension field to the dataset.

func (*Builder) Href

func (b *Builder) Href(s string) *Builder

Href sets the dataset's canonical URL.

func (*Builder) Label

func (b *Builder) Label(s string) *Builder

Label sets the dataset's human-readable title.

func (*Builder) Note

func (b *Builder) Note(s string) *Builder

Note appends a free-text note to the dataset.

func (*Builder) Source

func (b *Builder) Source(s string) *Builder

Source sets the dataset's source attribution text.

func (*Builder) SparseValues

func (b *Builder) SparseValues(m map[int]float64) *Builder

SparseValues sets the dataset's value as a sparse JSON-stat object: only the supplied flat-index/value pairs are present, every other cell is considered missing. The resulting Dataset encodes back to the sparse form.

func (*Builder) StatusArray

func (b *Builder) StatusArray(arr []string) *Builder

StatusArray sets a per-cell status in array form: one entry per cell in row-major order. Entries that are the empty string encode as missing in the resulting object form (see Builder.StatusMap).

func (*Builder) StatusMap

func (b *Builder) StatusMap(m map[int]string) *Builder

StatusMap sets a per-flat-index status in object form: only the supplied flat-index/status pairs are present; every other cell has no status.

func (*Builder) UniformStatus

func (b *Builder) UniformStatus(s string) *Builder

UniformStatus sets a single status string that applies to every cell. This is the most compact status form and overrides any earlier status call.

func (*Builder) Updated

func (b *Builder) Updated(s string) *Builder

Updated sets the dataset's last-updated timestamp. Stored verbatim; callers are responsible for ISO 8601 formatting.

func (*Builder) Values

func (b *Builder) Values(vals []float64) *Builder

Values sets the dataset's value array as a dense JSON-stat array. The slice must have length equal to the product of every dimension's category count, in row-major order (last declared dimension varies fastest). Every cell is treated as present; to mark specific cells as missing (JSON null), use Builder.DenseValuesWithMissing.

type Category

type Category struct {
	// ID is the category's identifier string. It is unique within its
	// dimension.
	ID string

	// Index is the 0-based position of this category within its dimension's
	// category list. This corresponds to the value found in the dimension's
	// category.index object (when present) and is used directly in the
	// row-major flat-index calculation.
	Index int

	// Label is the human-readable name of this category (e.g. "Spain"). May
	// be empty when the source document omitted category.label.
	Label string

	// Unit is the unit metadata for this category. Only metric-role
	// categories typically carry a Unit; for other categories it is nil.
	Unit *Unit

	// Coordinates is the optional geographic coordinate pair attached to a
	// category in a geo-role dimension. It has length 2: [longitude,
	// latitude]. nil when not present.
	Coordinates []float64
}

Category is one possible value of a Dimension. For example, in a "geo" dimension with size 2, the two categories might be "ES" and "FR".

Category identity is the Category.ID string (e.g. "ES"); Category.Index is its 0-based position in the dimension's category order, which together with the dataset strides pinpoints the cell positions holding its values.

type Cell

type Cell struct {
	// Value is the observation's numeric value. Always 0 when Missing is
	// true.
	Value float64

	// Status is the observation-level status code. May be empty even when
	// the value is present.
	Status string

	// Missing reports whether the cell has no value (JSON null in a dense
	// array, or absent key in a sparse object).
	Missing bool

	// Flat is the row-major flat index of the cell within the dataset's
	// value array. Always ≥ 0.
	Flat int

	// Coords is the per-dimension category index of the cell, in
	// [Dataset.ID] order.
	Coords []int
}

Cell is a single value-status pair read out of a dataset cube. It is the return type of the cell-access methods on Dataset.

A cell may be missing: dense value arrays encode missing observations as JSON null, and sparse value objects omit them. Missing cells have Value set to the zero float64 (0) and Missing set to true; callers should test Missing before reading Value.

func (Cell) HasValue

func (c Cell) HasValue() bool

HasValue reports whether the cell carries a numeric observation.

type Collection

type Collection struct {
	// Label is the collection's human-readable title.
	Label string

	// Href is the collection's canonical URL.
	Href string

	// Updated is the collection's last-updated timestamp in ISO 8601 format.
	Updated string

	// Source is the collection's source attribution text.
	Source string

	// Note is the collection-level notes.
	Note []string

	// Link holds the collection's links. Items appear here with rel "item".
	Link []Link

	// Extension collects provider-specific extra fields.
	Extension map[string]any

	// Items is the parsed item list, in declaration order.
	Items []Item
}

Collection is the in-memory representation of a JSON-stat collection response (class "collection"). A collection is an ordered list of Item values, each of which may embed or link to a dataset, dimension, or another collection.

func (*Collection) Datasets

func (c *Collection) Datasets() []*Dataset

Datasets returns every embedded dataset in the collection, in declaration order. Items that only link (without embedding) are skipped.

func (*Collection) ItemByID

func (c *Collection) ItemByID(id string) (*Item, error)

ItemByID returns the first item whose Item.ID matches id, or ErrItemNotFound when absent. ID equality is exact-string.

func (*Collection) ItemByIndex

func (c *Collection) ItemByIndex(i int) (*Item, error)

ItemByIndex returns the i-th item, or ErrItemNotFound when out of range.

func (*Collection) ItemsByClass

func (c *Collection) ItemsByClass(class string) []*Item

ItemsByClass returns the items whose embedded class matches the given JSON-stat class string (e.g. ClassDataset).

func (*Collection) MarshalJSON

func (c *Collection) MarshalJSON() ([]byte, error)

MarshalJSON makes *Collection satisfy json.Marshaler.

type Content

type Content string

Content controls whether categories are identified by label or by ID in the Transform output. Mirrors the JS Toolkit's opts.content.

const (
	// ContentLabel (the default) identifies categories by their human-readable
	// label.
	ContentLabel Content = "label"
	// ContentID identifies categories by their ID.
	ContentID Content = "id"
)

type Coordinates

type Coordinates map[string]string

Coordinates is the dimension-ID → category-ID map exposed to the UnflattenFunc callback for the current cell. It is freshly allocated per cell so the callback may retain references safely. Category IDs are always strings (e.g. the "year" dimension exposes {"year": "2012"}, not 2012).

type DataTable

type DataTable struct {
	Cols []DataTableCol `json:"cols"`
	Rows []DataTableRow `json:"rows"`
}

DataTable is the Google DataTable {cols, rows} shape produced by Transform with the TransformObject type. See https://developers.google.com/chart/interactive/docs/reference#DataTable.

type DataTableCol

type DataTableCol struct {
	ID    string `json:"id,omitempty"`
	Type  string `json:"type"`
	Label string `json:"label,omitempty"`
}

DataTableCol is one column descriptor in a DataTable.

type DataTableRow

type DataTableRow struct {
	C map[string]any `json:"c"`
}

DataTableRow is one row of a DataTable. Cells carry the value in `v` and an optional `f` (formatted) — here only `v` is populated, matching the JS Toolkit's behaviour.

type Datapoint

type Datapoint struct {
	Value   float64
	Status  string
	Missing bool
}

Datapoint is the value/status pair exposed to the UnflattenFunc callback for the current cell. When the cell carries no status, Status is the empty string (matching the JS Toolkit, which exposes null but uses "" here to stay Go-friendly). When the cell is missing (JSON null in a dense value array, or absent key in a sparse object), Value is 0 and Missing is true.

type Dataset

type Dataset struct {
	// Class is always [ClassDataset] for a Dataset. Present so that a
	// *Dataset satisfies the shared [Response] contract.
	Class string

	// Version is the JSON-stat version declared on the source document, or
	// [FormatVersion] for documents built with the [Builder].
	Version string

	// Label is the dataset's human-readable title.
	Label string

	// Href is the dataset's canonical URL, if declared.
	Href string

	// Source is the dataset's source attribution text.
	Source string

	// Updated is the dataset's last-updated timestamp in ISO 8601 format.
	// Stored as the raw string to avoid timezone pitfalls; callers that need
	// a time.Time should parse it themselves.
	Updated string

	// ID is the ordered list of dimension IDs that define the cube's axes.
	// The order matters: it determines row-major flat-index math.
	ID []string

	// Size is the number of categories per dimension, in the same order as
	// ID.
	Size []int

	// Role maps declared roles (time, geo, metric) to the dimension IDs that
	// carry them. May be empty.
	Role map[Role][]string

	// Dimensions holds each dimension by ID. Order is preserved from ID.
	Dimensions map[string]*Dimension

	// Note is the dataset-level notes array. Empty when absent.
	Note []string

	// Link is the dataset's related-resource links.
	Link []Link

	// Extension collects provider-specific extra fields attached to the
	// dataset itself. Anything that does not map onto a typed field ends up
	// here.
	Extension map[string]any
	// contains filtered or unexported fields
}

Dataset is the in-memory representation of a JSON-stat dataset response (class "dataset"). It is the primary type of this package: every read, traverse, subset, transform, and serve operation is a function over a *Dataset.

Dataset normalises the polymorphic parts of the wire format:

  • The "value" property can be a dense JSON array (with nulls) or a sparse JSON object; both are represented as a [valueStore] with a uniform float64 slice and a "missing" set.
  • The "status" property can be a string, an array, or an object; it is represented as a [statusStore] that resolves the effective status per cell.

On encode, Dataset re-emits the same form it was decoded from (or the form chosen by the Builder), preserving round-trip fidelity on canonical input.

func (*Dataset) CellByCoord

func (d *Dataset) CellByCoord(coords ...int) (Cell, error)

CellByCoord returns the Cell at the coordinates described by one category index per dimension, in Dataset.ID order. The number of coordinates must match Dataset.DimCount, and each must be in range for its dimension.

In the JS Toolkit this corresponds to `Dataset.Data([0,0,0])` with an array of integer category indices.

func (*Dataset) CellByFlat

func (d *Dataset) CellByFlat(i int) (Cell, error)

CellByFlat returns the Cell at the given flat row-major index. The index must satisfy 0 ≤ i < Dataset.N. Out-of-range indices return a ValueError wrapping ErrIndexOutOfRange.

In the JS Toolkit this corresponds to `Dataset.Data(i)` with a single integer argument.

func (*Dataset) CellByLabel

func (d *Dataset) CellByLabel(m map[string]string) (Cell, error)

CellByLabel resolves a cell from a map of dimension ID → category ID. The map must contain exactly one entry per dimension (use the empty string as a category ID for single-category dimensions when the source omitted the category.index). Unknown dimension or category IDs return the corresponding sentinel wrapped in a ValueError.

In the JS Toolkit this corresponds to `Dataset.Data({TIME:2020, GEO:"ES"})`.

func (*Dataset) Cells

func (d *Dataset) Cells() iter.Seq2[Cell, error]

Cells returns a lazy iterator over every cell of the cube, in row-major order. It is the Go 1.23 range-over-func counterpart of the JS Toolkit's `Dataset.Data()` (called with no arguments).

Usage:

for cell, err := range ds.Cells() {
    if err != nil { return err }
    _ = cell
}

The iterator yields exactly Dataset.N cells before stopping. Missing cells (JSON null in a dense array, or absent keys in a sparse object) are yielded with Cell.Missing set to true; they are not errors. The error return is reserved for malformed datasets whose strides cannot be computed — which, by the time a Dataset has been decoded or built, should not occur.

The yielded Cell.Coords slice is freshly allocated per yield; callers may retain references to past cells without copying. The iterator does not allocate any large per-cell slice.

func (*Dataset) CellsSlice

func (d *Dataset) CellsSlice() ([]Cell, error)

CellsSlice materialises the full cube as a slice of cells. It is the non-iterator fallback for callers on older Go or for code that needs random post-iteration access. Equivalent to collecting Dataset.Cells.

func (*Dataset) Dice

func (d *Dataset) Dice(f Filter, opts ...DiceOption) (*Dataset, error)

Dice subsets the cube, keeping (or, with WithDropFilter, removing) the categories named by f. It is the single subsetting operation in jsonstat-go: there is no Slice. Dice never removes dimensions from the cube — only categories. A dimension not mentioned in f is left untouched.

Dice returns a new Dataset whose dimensions carry the retained categories in their original order, whose Size reflects the new category counts, and whose value/status stores are reindexed to match. The receiver is not mutated.

An empty filter (or nil) returns a deep clone of the dataset. A filter that removes every category of some dimension produces a dataset with N()==0.

func (*Dataset) DimCount

func (d *Dataset) DimCount() int

DimCount returns the number of dimensions.

func (*Dataset) Dimension

func (d *Dataset) Dimension(id string) (*Dimension, error)

Dimension returns the dimension with the given ID, or ErrDimensionNotFound when no such dimension exists.

func (*Dataset) DimensionByIndex

func (d *Dataset) DimensionByIndex(i int) (*Dimension, error)

DimensionByIndex returns the dimension at position i in Dataset.ID. Returns ErrIndexOutOfRange when i is out of range.

func (*Dataset) DimensionByRole

func (d *Dataset) DimensionByRole(role Role) (*Dimension, error)

DimensionByRole returns the first dimension assigned the given role, or ErrDimensionNotFound when no dimension carries that role. For the (rare) case of multiple dimensions sharing a role, use Dataset.DimensionsByRole.

func (*Dataset) DimensionIDs

func (d *Dataset) DimensionIDs() []string

DimensionIDs returns a defensive copy of Dataset.ID.

func (*Dataset) DimensionPosition

func (d *Dataset) DimensionPosition(id string) (int, error)

DimensionPosition returns the index in Dataset.ID of the given dimension ID, or ErrDimensionNotFound when absent.

func (*Dataset) DimensionsByRole

func (d *Dataset) DimensionsByRole(role Role) []*Dimension

DimensionsByRole returns every dimension assigned the given role, in the order they appear in Dataset.ID.

func (*Dataset) HasRole

func (d *Dataset) HasRole(role Role) bool

HasRole reports whether any dimension is assigned the given role.

func (*Dataset) MarshalJSON

func (d *Dataset) MarshalJSON() ([]byte, error)

MarshalJSON makes *Dataset satisfy json.Marshaler so it can be embedded in other JSON structures or marshalled directly.

func (*Dataset) N

func (d *Dataset) N() int

N returns the total number of cells in the cube (the product of Size), including missing ones. This is the Go counterpart of the JS Toolkit's `n` property. Returns 0 if the dataset has no dimensions or any dimension has size 0.

func (*Dataset) Roles

func (d *Dataset) Roles() []Role

Roles returns the set of roles declared on the dataset.

func (*Dataset) SizeSlice

func (d *Dataset) SizeSlice() []int

SizeSlice returns a defensive copy of Dataset.Size.

func (*Dataset) StatusFormat

func (d *Dataset) StatusFormat() string

StatusFormat reports which wire form the dataset's status store would emit on encode.

func (*Dataset) Strides

func (d *Dataset) Strides() []int

Strides returns the cached strides for this dataset. The returned slice is a defensive copy; callers may mutate it. Returns nil before [reindex] is called (which Decode and Builder do automatically).

func (*Dataset) Transform

func (d *Dataset) Transform(opts ...TransformOption) (any, error)

Transform converts the dataset to tabular form. It is the Go counterpart of the JS Toolkit's `Dataset.Transform(opts)`, built on top of Dataset.Unflatten and using the same options vocabulary. It returns one of:

When WithMeta is true the array/objarr shapes are wrapped in *TableWithMeta carrying the dataset metadata block, matching the JS Toolkit's `{meta, data}` shape. The object type never emits metadata.

An empty (nil) dataset returns (nil, nil).

func (*Dataset) Unflatten

func (d *Dataset) Unflatten(fn UnflattenFunc) ([]any, error)

Unflatten walks every cell of the cube, invoking fn for each, and returns the slice of non-nil results. It is the Go counterpart of the JS Toolkit's `Dataset.Unflatten(cb)`.

fn returns any → appended to the result; nil → cell dropped.

An empty (nil) dataset or a nil callback returns (nil, nil).

Usage (mirrors the JS example that returns {coordinates, datapoint}):

out, err := ds.Unflatten(func(coords jsonstat.Coordinates, dp jsonstat.Datapoint, n int, row []any) any {
    return map[string]any{"coordinates": coords, "datapoint": dp}
})

func (*Dataset) ValueFormat

func (d *Dataset) ValueFormat() string

ValueFormat reports which wire form the dataset's value store would emit on encode. See [valueStore].

func (*Dataset) Walk

func (d *Dataset) Walk(fn func(Cell) error) error

Walk calls fn for every cell in the cube in row-major order, stopping at the first non-nil error returned by fn. It is the Go counterpart of the JS Toolkit's `Dataset.Unflatten(cb)`.

Walk allocates one Cell per cell of the cube (reused via the same path as Dataset.Cells); fn receives a value copy so it may retain cells safely.

type DiceOption

type DiceOption func(*diceConfig)

DiceOption configures a Dataset.Dice operation.

func WithClone

func WithClone(b bool) DiceOption

WithClone controls whether Dice returns a deep copy of the receiver or is permitted to mutate it in place. The default is true (clone); passing false is an opt-in optimisation for callers that will not use the receiver again.

func WithDropFilter

func WithDropFilter(b bool) DiceOption

WithDropFilter inverts the meaning of the Filter passed to Dataset.Dice: listed categories are removed rather than retained. Dimensions absent from the filter are still left untouched.

type DimBuilder

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

DimBuilder accumulates categories (and optional role/label) for a single dimension. It is returned by Builder.Dim and closed by DimBuilder.End, which returns control to the parent Builder.

func (*DimBuilder) Cat

func (d *DimBuilder) Cat(id, label string) *DimBuilder

Cat appends a category to the current dimension. An empty label is allowed (the category will render under its ID).

func (*DimBuilder) CatWith

func (d *DimBuilder) CatWith(c catSpec) *DimBuilder

CatWith attaches a builder-configured category (with optional unit or coordinates) to the current dimension. It is the entry point for the richer per-category metadata the simple DimBuilder.Cat does not expose.

func (*DimBuilder) Coordinates

func (d *DimBuilder) Coordinates(lon, lat float64) *DimBuilder

Coordinates attaches a [lon, lat] pair to the most recently added category. Meaningful only for geo-role dimensions.

func (*DimBuilder) End

func (d *DimBuilder) End() *Builder

End closes the current dimension and returns the parent Builder.

func (*DimBuilder) Role

func (d *DimBuilder) Role(r Role) *DimBuilder

Role declares the JSON-stat role carried by this dimension (time, geo, or metric). Multiple dimensions may share a role.

func (*DimBuilder) Unit

func (d *DimBuilder) Unit(u *Unit) *DimBuilder

Unit attaches a Unit to the most recently added category of this dimension. Metric dimensions typically carry a unit on each category.

type Dimension

type Dimension struct {
	// ID is the dimension's identifier, matching one entry of [Dataset.ID].
	ID string

	// Label is the human-readable name of the dimension (e.g. "Geography").
	Label string

	// Role is the special role assigned to this dimension by the dataset's
	// "role" object, or [RoleUnknown] (the empty string) when no role is set.
	Role Role

	// Size is the number of categories in this dimension. It equals
	// [Dataset.Size][i] for the dimension's position i in [Dataset.ID].
	Size int

	// Categories lists this dimension's categories in index order. The slice
	// has length Size.
	Categories []Category

	// Child captures the category.child hierarchy: parent category ID → list
	// of child category IDs. nil when the dimension has no hierarchy. See
	// https://json-stat.org/format/#child.
	Child map[string][]string

	// Href is the URL of the standalone "dimension" response for this
	// dimension, if the source document provided one.
	Href string

	// Link is the list of related resources for this dimension (e.g.
	// "alternate" geographic breakdowns). Empty when absent.
	Link []Link

	// Note is the dimension-level notes. Empty when absent.
	Note []string

	// Extension collects provider-specific extra fields attached to this
	// dimension.
	Extension map[string]any
	// contains filtered or unexported fields
}

Dimension describes one axis of a dataset's cube. JSON-stat stores dimensions under the dataset's "dimension" object keyed by dimension ID; the order in the dataset's "id" array defines the cube's axis order and determines stride math.

jsonstat-go attaches a few precomputed conveniences to Dimension (Size, Categories, and the lookup maps) so that traversal methods do not have to re-walk the JSON-stat structure on every call.

func (*Dimension) CategoryByID

func (d *Dimension) CategoryByID(id string) (*Category, error)

CategoryByID returns the category with the given ID, or ErrCategoryNotFound if no such category exists in this dimension.

func (*Dimension) CategoryIDs

func (d *Dimension) CategoryIDs() []string

CategoryIDs returns the ID of every category in index order.

func (*Dimension) CategoryIndex

func (d *Dimension) CategoryIndex(id string) (int, error)

CategoryIndex returns the 0-based position of the category with the given ID within this dimension, or ErrCategoryNotFound if it is absent.

func (*Dimension) CategoryLabels

func (d *Dimension) CategoryLabels() []string

CategoryLabels returns the human-readable label of every category in index order. Categories without a label are represented by their ID. This is the Go counterpart of the JS Toolkit's `Dimension(id, true)` boolean flag.

func (*Dimension) ChildrenOf

func (d *Dimension) ChildrenOf(parentID string) ([]string, bool)

ChildrenOf returns the child category IDs of the given parent category ID, or false if the parent has no children (or the dimension has no hierarchy). The returned slice is a defensive copy; callers may mutate it freely.

func (*Dimension) HasHierarchy

func (d *Dimension) HasHierarchy() bool

HasHierarchy reports whether the dimension declares a category.child hierarchy.

func (*Dimension) MarshalJSON

func (d *Dimension) MarshalJSON() ([]byte, error)

MarshalJSON makes *Dimension satisfy json.Marshaler for the standalone dimension response class.

type Document

type Document struct {
	// Class is the JSON-stat response class declared on the document. It is
	// one of [ClassDataset], [ClassCollection], [ClassDimension], or
	// [ClassBundle] (the legacy pre-2.0 marker, synthesised by Decode).
	Class string

	// Version is the JSON-stat version declared on the document.
	Version string

	// Label is the document-level label, if any.
	Label string

	// Href is the document-level canonical URL, if any.
	Href string

	// Note is the document-level notes array.
	Note []string

	// Link is the document-level related-resource links.
	Link []Link

	// Extension collects provider-specific extra fields attached to the
	// document itself.
	Extension map[string]any

	// Dataset holds the parsed dataset when Class == [ClassDataset]. It is
	// nil otherwise.
	Dataset *Dataset

	// Collection holds the parsed collection when Class == [ClassCollection].
	Collection *Collection

	// Dimension holds the parsed standalone dimension when Class ==
	// [ClassDimension]. It is nil otherwise.
	Dimension *Dimension

	// Bundle holds the parsed datasets when the document is a pre-2.0 bundle
	// (a JSON object whose values are datasets, with no "class" property).
	// Keyed by dataset id. nil for 2.0 documents.
	Bundle map[string]*Dataset

	// IsBundle reports whether the document was decoded as a pre-2.0 bundle.
	IsBundle bool
}

Document is the top-level parsed value of a JSON-stat response. Depending on the source document's "class", exactly one of the fields below is populated:

  • class "dataset": Document.Dataset holds the single dataset. (Some 2.0 documents omit "class" entirely; Decode treats these as datasets when they have the required dataset properties.)
  • class "collection": Document.Collection holds the collection.
  • class "dimension": Document.Dimension holds the standalone dimension.

The pre-2.0 bundle container is also accepted: Document.Bundle is a map of datasets keyed by id, and Document.IsBundle reports this case. Bundles are decoded for backward compatibility but never re-emitted by Encode.

func Decode

func Decode(r io.Reader) (*Document, error)

Decode parses a JSON-stat 2.0 document from r and returns the corresponding Document. Decode accepts all three 2.0 response classes (dataset, collection, dimension) and tolerates the pre-2.0 bundle container: when the input has no "class" property but its top-level values look like datasets, Document.IsBundle is set and Document.Bundle is populated.

Decode normalises the polymorphic parts of the format:

  • dense (array) and sparse (object) "value" forms are both supported and exposed uniformly via the traversal methods.
  • the three "status" forms (string, array, object) are supported.

The document's structural shape is checked; semantic checks (size/value length agreement, role references, etc.) are performed by Validate.

func DecodeBytes

func DecodeBytes(b []byte) (*Document, error)

DecodeBytes is like Decode but accepts a byte slice.

func Fetch

func Fetch(ctx context.Context, url string, opts ...FetchOption) (*Document, error)

Fetch loads a JSON-stat document from url via HTTP and decodes it. It is the context-aware counterpart of Decode for remote sources. The supplied context.Context governs the entire request lifecycle, including connection setup and body reads; cancelling it aborts the call.

By default Fetch uses a fresh http.Client with a 30-second timeout. A shared client (connection pooling, custom transport, mTLS, etc.) can be supplied via WithHTTPClient.

fetchCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
doc, err := jsonstat.Fetch(fetchCtx, url,
    jsonstat.WithUserAgent("my-app/1.0"),
    jsonstat.WithMaxBytes(50*1024*1024), // 50 MiB guard
)

func (*Document) Datasets

func (d *Document) Datasets() []*Dataset

Datasets returns every dataset in the document, regardless of class:

  • dataset: a one-element slice holding Document.Dataset.
  • collection: a slice holding each embedded dataset.
  • bundle: a slice holding each bundled dataset, in id-key order.
  • otherwise: nil.

func (*Document) IsCollection

func (d *Document) IsCollection() bool

IsCollection reports whether the document is a collection response.

func (*Document) IsDataset

func (d *Document) IsDataset() bool

IsDataset reports whether the document is a single-dataset response.

func (*Document) IsDimension

func (d *Document) IsDimension() bool

IsDimension reports whether the document is a standalone dimension response.

func (*Document) MarshalJSON

func (doc *Document) MarshalJSON() ([]byte, error)

MarshalJSON makes *Document satisfy json.Marshaler, dispatching on the populated response-class field.

func (*Document) SingleDataset

func (d *Document) SingleDataset() (*Dataset, error)

SingleDataset is a convenience for the common case of a single-dataset document. It returns Document.Dataset when present, or ErrDatasetNotFound otherwise.

type FetchOption

type FetchOption func(*fetchConfig)

FetchOption configures a Fetch call.

func WithBody

func WithBody(r io.Reader) FetchOption

WithBody supplies a request body for non-GET methods. It is the caller's responsibility to also set an appropriate Content-Type via WithHeader.

func WithHTTPClient

func WithHTTPClient(c *http.Client) FetchOption

WithHTTPClient supplies the http.Client used by Fetch. When not set, Fetch uses a per-call client with a 30-second timeout (overridable via the request context.Context).

func WithHeader

func WithHeader(key, value string) FetchOption

WithHeader sets or overrides a single request header on the outgoing GET. Calling it multiple times with different keys accumulates; calling it twice with the same key replaces the value. "Host" cannot be set this way.

func WithMaxBytes

func WithMaxBytes(n int64) FetchOption

WithMaxBytes caps the number of bytes Fetch will read from the response body. A response whose body exceeds the cap is aborted with ErrResponseTooLarge. A value of 0 (the default) means unlimited.

func WithMethod

func WithMethod(m string) FetchOption

WithMethod overrides the HTTP method used by Fetch. The default is GET. When set to "POST", WithBody should be supplied as well.

func WithUserAgent

func WithUserAgent(ua string) FetchOption

WithUserAgent sets the User-Agent header on the outgoing request. This is a shortcut for WithHeader("User-Agent", …) but also records the value for use when no headers are otherwise set.

type Field

type Field string

Field controls whether the dimension, value and status column keys are IDs or labels. Mirrors the JS Toolkit's opts.field. The default is FieldID, except for the "array" type whose default is FieldLabel (matching JS).

const (
	// FieldID keys columns by dimension/value/status IDs.
	FieldID Field = "id"
	// FieldLabel keys columns by dimension/value/status labels.
	FieldLabel Field = "label"
)

type Filter

type Filter map[string][]string

Filter selects the categories of a Dataset to keep (or, when WithDropFilter is set, to exclude) during a Dataset.Dice operation. The map is keyed by dimension ID; each value is the list of category IDs in that dimension to retain (or drop).

A dimension absent from the map is left untouched (all of its categories are kept). Category IDs that do not exist in their dimension cause Dice to return ErrCategoryNotFound. Duplicate IDs and ordering are ignored: the resulting dimension always preserves the original category order of the source.

type Item

type Item struct {
	// Href is the URL of the item, when it is referenced rather than
	// embedded.
	Href string

	// Type is the MIME type of the item at Href.
	Type string

	// Label is the human-readable item title.
	Label string

	// Class is the embedded item's class (e.g. "dataset"), when the item is
	// embedded rather than only linked.
	Class string

	// ID is the optional item identifier inside the collection. Some
	// providers add an "id" extension to disambiguate items.
	ID string

	// Extension collects provider-specific extra fields attached to the
	// item's link entry.
	Extension map[string]any

	// EmbeddedDataset is non-nil when the item embeds a full dataset.
	EmbeddedDataset *Dataset

	// EmbeddedDimension is non-nil when the item embeds a standalone
	// dimension.
	EmbeddedDimension *Dimension
}

Item is one entry in a JSON-stat collection. JSON-stat's collection class uses a "link" array with rel "item" to enumerate its contents; each item may embed a full dataset, dimension, or another collection, or merely link to one via Href.

func (*Item) IsEmbedded

func (i *Item) IsEmbedded() bool

IsEmbedded reports whether the item carries an embedded response rather than only a link.

type Link struct {
	// Href is the target URL of the link.
	Href string `json:"href,omitempty"`

	// Type is the MIME type of the resource at Href. For example
	// "application/json-stat".
	Type string `json:"type,omitempty"`

	// Rel is the IANA link relation type. Common values in JSON-stat include
	// "self", "item", "alternate", "version", and "note".
	Rel string `json:"rel,omitempty"`

	// Label is a human-readable description of the link target.
	Label string `json:"label,omitempty"`

	// Extension carries provider-specific extra fields. JSON-stat allows
	// arbitrary keys inside link objects beyond href/type/rel/label; they are
	// collected here.
	Extension map[string]any `json:"-"`
}

Link represents a single entry in a JSON-stat "link" array. Links describe related resources via the IANA link relation type ("rel") and a target URL ("href"). They are used at every level of a JSON-stat document: the document itself, individual datasets, dimensions, and categories.

The collection response class uses links with rel "item" to enumerate its contents; the dimension response class uses links with rel "self" and "related" (e.g. alternate geographic breakdowns).

See https://json-stat.org/format/#link.

type Role

type Role string

Role names a special meaning assigned to a dimension via the dataset's "role" object. JSON-stat 2.0 defines exactly three roles: time, geo, and metric. See https://json-stat.org/format/#role.

const (
	// RoleTime identifies a dimension whose categories are points or periods
	// in time (e.g. "2020", "2021-Q1").
	RoleTime Role = "time"
	// RoleGeo identifies a spatial dimension whose categories are places
	// (e.g. country codes, NUTS regions).
	RoleGeo Role = "geo"
	// RoleMetric identifies a dimension whose categories name the quantity
	// being measured (e.g. "POP", "GDP"). Metric categories typically carry
	// [Unit] metadata via [Category.Unit].
	RoleMetric Role = "metric"
)

type TableDimCategories

type TableDimCategories struct {
	ID    []string `json:"id"`
	Label []string `json:"label"`
}

TableDimCategories holds the parallel category ID/label arrays.

type TableDimMeta

type TableDimMeta struct {
	Label      string             `json:"label"`
	Role       string             `json:"role,omitempty"`
	Categories TableDimCategories `json:"categories"`
}

TableDimMeta is the per-dimension metadata attached under TableMeta.

type TableMeta

type TableMeta struct {
	Type       string                  `json:"type"`
	Label      string                  `json:"label,omitempty"`
	Source     string                  `json:"source,omitempty"`
	Updated    string                  `json:"updated,omitempty"`
	ID         []string                `json:"id"`
	Status     bool                    `json:"status"`
	By         string                  `json:"by,omitempty"`
	Drop       []string                `json:"drop,omitempty"`
	Prefix     string                  `json:"prefix,omitempty"`
	Comma      bool                    `json:"comma"`
	Dimensions map[string]TableDimMeta `json:"dimensions,omitempty"`
}

TableMeta is the metadata block attached by WithMeta. Only the fields the JS Toolkit includes are populated; provider-specific extension is left to callers via the Dataset.Extension map (not auto-copied here).

type TableWithMeta

type TableWithMeta struct {
	Meta TableMeta `json:"meta"`
	Data any       `json:"data"`
}

TableWithMeta wraps a Transform result with the dataset metadata block, emitted when WithMeta is true. It mirrors the JS Toolkit's {meta, data} shape exactly so that JSON serialisation is byte-compatible.

type TransformOption

type TransformOption func(*transformConfig)

TransformOption configures Dataset.Transform. The functional-options pattern keeps the call sites self-documenting.

func WithBy

func WithBy(dimID string) TransformOption

WithBy pivots the value column on the given dimension (one output column per category of that dimension). Only supported by the arrobj and objarr types (silently ignored for the array and object types, matching JS). When `by` names a non-existent dimension, it is ignored.

func WithComma

func WithComma(b bool) TransformOption

WithComma formats numbers as strings with a comma decimal mark. Default false. Not available with the object type.

func WithContent

func WithContent(ct Content) TransformOption

WithContent controls whether categories are identified by label or by ID. Default ContentLabel.

func WithDrop

func WithDrop(dimIDs ...string) TransformOption

WithDrop excludes the listed dimension IDs from the output. Invalid dimension IDs and multi-category dimensions are ignored (single-category dimensions are the only ones droppable, matching the JS rule).

func WithField

func WithField(f Field) TransformOption

WithField controls whether dimension, value and status keys are IDs or labels. Default FieldID (the array type overrides this to FieldLabel).

func WithMeta

func WithMeta(b bool) TransformOption

WithMeta attaches dataset metadata to the output, wrapping it as *TableWithMeta. Default false. Not available with the object type.

func WithPrefix

func WithPrefix(s string) TransformOption

WithPrefix sets a prefix prepended to the pivoted category columns when WithBy is in effect. Default "".

func WithStatus

func WithStatus(b bool) TransformOption

WithStatus includes the status column in the output. Default false. Ignored when WithBy is also set (matching JS behaviour).

func WithStatusLabel

func WithStatusLabel(s string) TransformOption

WithStatusLabel sets the label of the status column. Default "Status". Only relevant when WithStatus is true.

func WithTransformType

func WithTransformType(t TransformType) TransformOption

WithTransformType sets the output shape. Default TransformArray.

func WithValueLabel

func WithValueLabel(s string) TransformOption

WithValueLabel sets the label of the value column. Default "Value".

type TransformType

type TransformType string

TransformType selects the output shape of Dataset.Transform. It mirrors the JS Toolkit's opts.type string values.

const (
	// TransformArray is the default type: an array of arrays whose first
	// element is a header row. Mirrors JS type "array".
	TransformArray TransformType = "array"

	// TransformArrObj produces an array of per-cell objects keyed by dimension
	// ID, "value" and "status". Mirrors JS type "arrobj". Supports the `by`
	// (pivot) and `meta` options.
	TransformArrObj TransformType = "arrobj"

	// TransformObjArr produces a columnar object: each property is a parallel
	// array. Mirrors JS type "objarr". Supports the `by` (pivot) and `meta`
	// options.
	TransformObjArr TransformType = "objarr"

	// TransformObject produces a Google DataTable {cols, rows}. Mirrors JS
	// type "object". Infers column type ("number" or "string") from the first
	// value.
	TransformObject TransformType = "object"
)

type UnflattenFunc

type UnflattenFunc func(coords Coordinates, dp Datapoint, n int, row []any) any

UnflattenFunc mirrors the JS Toolkit's Unflatten callback. It is invoked once per cell in row-major order. The arguments are:

  • coords: the Coordinates of the cell (dimension ID → category ID).
  • dp: the cell's value/status pair (Datapoint).
  • n: the 0-based cell counter (the flat row-major index).
  • row: the accumulator slice being built; the value returned by the callback is appended to it after the callback returns.

Returning nil skips the cell (it is not appended), matching the JS Toolkit's treatment of a callback that returns undefined.

type Unit

type Unit struct {
	// Decimals is the recommended number of decimal places to display. It is
	// required by the spec whenever a unit object is present, but jsonstat-go
	// does not enforce that on the type itself — that is a validation rule,
	// see [Validate].
	Decimals int `json:"decimals,omitempty"`

	// Label is a human-readable unit label (e.g. "persons", "euros"). It is
	// typically appended after the value.
	Label string `json:"label,omitempty"`

	// Symbol is a unit symbol (e.g. "$", "%", "€"). When both Label and
	// Symbol are present, JSON-stat providers conventionally use Symbol for
	// compact display and Label for accessibility.
	Symbol string `json:"symbol,omitempty"`

	// Position governs where the symbol is placed relative to the value.
	// "end" (the default) renders "5%"; "start" renders "$5".
	Position string `json:"position,omitempty"`
}

Unit describes the unit of measure attached to a metric category. It is the Go representation of the "unit" object inside dimension.<id>.category.unit.<catid>. See https://json-stat.org/format/#unit.

JSON-stat requires decimals whenever unit is present. The other fields are optional.

type ValidationCode

type ValidationCode string

ValidationCode is a stable identifier for a single class of validation problem. Codes are kebab-case strings, kept short so they fit on one line of structured-log output. Callers may switch on them to drive user-facing messages or selective ignoring.

const (
	// CodeMissingClass: the document has no "class" property and is not a
	// tolerated pre-2.0 bundle.
	CodeMissingClass ValidationCode = "missing-class"
	// CodeUnknownClass: the document's "class" is not one of dataset,
	// collection, or dimension.
	CodeUnknownClass ValidationCode = "unknown-class"
	// CodeMissingRequired: a JSON-stat 2.0 required property is absent.
	CodeMissingRequired ValidationCode = "missing-required"
	// CodeIDSizeMismatch: len(id) != len(size).
	CodeIDSizeMismatch ValidationCode = "id-size-mismatch"
	// CodeDimMissing: a dimension named in id is absent from dimension.
	CodeDimMissing ValidationCode = "dim-missing"
	// CodeDimExtra: the dimension object has a key not present in id.
	CodeDimExtra ValidationCode = "dim-extra"
	// CodeCategorySizeMismatch: a dimension's category count != its size.
	CodeCategorySizeMismatch ValidationCode = "category-size-mismatch"
	// CodeInvalidSize: size contains a negative element or is otherwise
	// malformed.
	CodeInvalidSize ValidationCode = "invalid-size"
	// CodeValueLengthMismatch: dense value array length != prod(size).
	CodeValueLengthMismatch ValidationCode = "value-length-mismatch"
	// CodeSparseValueKeyOutOfRange: a sparse value object has a key ≥ prod(size).
	CodeSparseValueKeyOutOfRange ValidationCode = "sparse-value-key-out-of-range"
	// CodeStatusLengthMismatch: status array length != prod(size).
	CodeStatusLengthMismatch ValidationCode = "status-length-mismatch"
	// CodeRoleReferencesUnknownDim: role names a dimension not in id.
	CodeRoleReferencesUnknownDim ValidationCode = "role-unknown-dim"
	// CodeDuplicateCategoryID: a dimension has two categories with the same ID.
	CodeDuplicateCategoryID ValidationCode = "duplicate-category-id"
	// CodeDuplicateDimID: id contains the same dimension ID twice.
	CodeDuplicateDimID ValidationCode = "duplicate-dim-id"
)

Stable validation codes. The set is intentionally small: each identifies a single invariant, regardless of which tier (structural or semantic) it belongs to. Tests and downstream tooling can rely on these strings not changing across releases.

type ValidationError

type ValidationError struct {
	// Code is the stable identifier (see the Code* constants).
	Code ValidationCode
	// Path locates the problem using dotted-path notation, e.g.
	// "dimension.geo.category.index" or "value[3]". The empty string means
	// "the document as a whole".
	Path string
	// Message is a human-readable description.
	Message string
	// Err is the underlying sentinel error (one of the Err* package-level
	// values), suitable for errors.Is.
	Err error
}

ValidationError describes a single validation problem on a Document or Dataset. A slice of ValidationError is returned by Validate; the slice is empty when the input is valid.

func Validate

func Validate(doc *Document) []ValidationError

Validate runs the structural and semantic tiers over doc and returns every problem found, in path order. An empty (nil) slice means the document is valid. Problems are never silently dropped: a single missing property can produce several cascading errors, and Validate surfaces all of them so callers can show a complete report.

Validate never panics. A nil document yields a single CodeMissingClass error.

func ValidateDataset

func ValidateDataset(ds *Dataset, prefix string) []ValidationError

ValidateDataset runs the structural and semantic tiers over a single Dataset. The optional prefix is prepended to every reported Path so bundle/collection members can be distinguished.

func (*ValidationError) Error

func (v *ValidationError) Error() string

Error implements error. Validation errors are usually collected into a slice, but a single ValidationError is itself a valid error so it can be returned from one-shot helpers.

func (*ValidationError) HasSeverity

func (v *ValidationError) HasSeverity() string

HasSeverity reports whether the validation error is fatal (a dataset with any fatal error cannot be safely traversed or encoded). The current rule treats every code as fatal except CodeDimExtra, which is a permissive warning about extra keys in the dimension object.

func (*ValidationError) Unwrap

func (v *ValidationError) Unwrap() error

Unwrap exposes the wrapped sentinel for errors.Is / errors.As.

type ValueError

type ValueError struct {
	// Op is the operation that failed (e.g. "Decode", "Dataset.Dimension").
	Op string
	// Dim is the dimension ID involved, if any.
	Dim string
	// Cat is the category ID involved, if any.
	Cat string
	// Flat is the flat row-major cell index involved, if any. -1 means
	// "not applicable".
	Flat int
	// Err is the wrapped sentinel or underlying error.
	Err error
}

ValueError carries context about where in the cube an error occurred. It is the structured wrapping type used by Decode, Validate, and the traversal methods. Callers can use errors.As to recover the structured context.

func (*ValueError) Error

func (v *ValueError) Error() string

Error implements error.

func (*ValueError) Unwrap

func (v *ValueError) Unwrap() error

Unwrap allows errors.Is / errors.As to reach the wrapped sentinel.

Directories

Path Synopsis
cmd
jsonstat command
Command jsonstat is a thin command-line shell over the github.com/jsonstat/go library.
Command jsonstat is a thin command-line shell over the github.com/jsonstat/go library.
examples
basic-read command
Command basic-read shows the simplest end-to-end client flow:
Command basic-read shows the simplest end-to-end client flow:
build-cube command
Command build-cube constructs a small 3-dimensional JSON-stat dataset programmatically with the Builder, then writes the canonical JSON-stat wire form to stdout.
Command build-cube constructs a small 3-dimensional JSON-stat dataset programmatically with the Builder, then writes the canonical JSON-stat wire form to stdout.
serve-http command
Command serve-http builds a small cube in-memory and serves it over HTTP via the jsonstathttp handler.
Command serve-http builds a small cube in-memory and serves it over HTTP via the jsonstathttp handler.
internal
stride
Package stride implements the row-major ("what does not change, first") index math used by JSON-stat cubes.
Package stride implements the row-major ("what does not change, first") index math used by JSON-stat cubes.
Package jsonstathttp serves a JSON-stat dataset over net/http with query-string subsetting, content negotiation, conditional GET (ETag / If-None-Match), and slog access logging.
Package jsonstathttp serves a JSON-stat dataset over net/http with query-string subsetting, content negotiation, conditional GET (ETag / If-None-Match), and slog access logging.

Jump to

Keyboard shortcuts

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