Documentation
¶
Overview ¶
Package jsonkeys implements per-key JSON pointer merge used by adapters that need to own a subset of keys inside a shared JSON (or JSONC) config file.
Index ¶
- func ConvertNumbers(v any) any
- func DecodeJSONC(data []byte) (map[string]any, error)
- func DecodeObject(data []byte) (map[string]any, error)
- func DecodeYAML(yml []byte) (map[string]any, error)
- func MergeJSONC(existing []byte, ours map[string]any, ownedPointers []string) ([]byte, error)
- func MergeKeys(existing, ours map[string]any, ownedPointers []string) (map[string]any, []string, []string)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ConvertNumbers ¶ added in v0.11.0
ConvertNumbers walks v, replacing every json.Number with a Go numeric, recursing through maps and slices and leaving non-numeric values untouched. DecodeObject/DecodeJSONC deliberately KEEP json.Number so a merge re-marshals foreign JSON keys with their exact digits — but a json.Number that escapes into a TOML-persisted field is a hazard: go-toml marshals the underlying string type as a TOML *string* (`timeout = '30'`), silently flipping the value's type on the next render. Callers that persist decoded values outside JSON (the capture dest->source funnel, DecodeYAML) normalize through this first.
Range contract: an integer within int64 stays exact (snowflake ids and nanosecond timestamps beyond float64's 2^53 survive); an integer BEYOND int64 falls to float64 and is lossy — TOML integers are int64, so exactness past that bound is unrepresentable in the persisted form anyway; a numeric literal that parses as neither int64 nor float64 (e.g. `1e309`, `1e400`) falls back to its literal string, persisted as a TOML string — acceptable for an unrepresentable extreme, and the capture leak backstop still scans strings.
func DecodeJSONC ¶ added in v0.2.0
DecodeJSONC parses JSONC content (comments + trailing commas tolerated) into a map, preserving json.Number. Plain JSON parses identically. Used by ingest paths whose native file is JSONC.
func DecodeObject ¶
DecodeObject unmarshals JSON object bytes into a map, preserving numbers as json.Number rather than coercing to float64. The merge promises to preserve FOREIGN keys verbatim, but float64 silently rounds any integer larger than 2^53 (snowflake ids, nanosecond timestamps) on re-marshal. json.Number keeps the literal digits and re-marshals exactly. nil/empty input yields an empty map.
Lax at the edges by design, and slightly laxer than json.Unmarshal: content AFTER the first top-level value is ignored rather than refused (json.Decoder reads one value), so a truncated-concatenation like `{…}{…}` decodes its first object instead of erroring; a corrupt single value still errors. Every JSON config read — adapter ingests AND the key-merge machinery — shares these semantics, so tightening here would change how merges read existing native files; callers that need whole-input strictness must enforce it themselves. Pinned by TestDecodeObject_LaxEdges.
func DecodeYAML ¶
DecodeYAML parses YAML (e.g. component frontmatter) into a map, preserving integer precision beyond 2^53 (up to int64). A plain yaml.Unmarshal into interface{} decodes every number as float64, silently rounding a large integer (snowflake id, nanosecond timestamp) — and since yaml.Marshal then re-emits the rounded value, the corruption is persisted on render and on source write-back. This routes YAML→JSON (which keeps the integer literal), decodes with UseNumber, then normalizes through ConvertNumbers so yaml.Marshal re-emits a bare integer; see ConvertNumbers' range contract for the beyond-int64 and unparseable extremes. Empty/null input yields an empty map; a non-mapping document is an error (frontmatter must be a mapping).
func MergeJSONC ¶ added in v0.2.0
MergeJSONC merges ours into existing JSONC content, removing ownedPointers no longer present in ours. Foreign keys and values are preserved. It is the shared engine behind the "merge-jsonc-keys" strategy (OpenCode's opencode.json, Gemini's settings.json, and the generic breadth tier's commented settings files).
Strategy: parse existing JSONC (hujson, tolerating comments + trailing commas) -> Standardize to strict JSON -> MergeKeys -> emit via json.MarshalIndent. v1 trade-off: this Standardize+marshal path discards ALL comments and trailing-comma formatting (not just touched sections). The file is never corrupted and no data keys are lost; only JSONC formatting is. Comment-preserving mutation is deferred to v1.x.
Unparseable existing content is a returned error, never treated as empty — merging against an empty map would clobber every foreign key in the file.
func MergeKeys ¶
func MergeKeys(existing, ours map[string]any, ownedPointers []string) (map[string]any, []string, []string)
MergeKeys merges ours into existing, removing ownedPointers that are no longer in ours. Returns the merged map plus diagnostic lists.
kept: pointers from ownedPointers that are still present in ours. removed: pointers from ownedPointers that are absent from ours and were deleted from existing.
JSON pointer syntax: leading "/", "/" separated path segments. RFC 6901 escapes ("~0" for "~", "~1" for "/") are supported.
Types ¶
This section is empty.