Documentation
¶
Overview ¶
Package persist provides immutable, persistent values, maps, and vectors for JSON-like application data.
The public value domain is closed. A Value is nil, int64, float64, string, bool, Map, or Vector. A Key is the scalar subset accepted by Map: nil, int64, float64, string, or bool. The package does not accept arbitrary Go objects, reflection-based values, generic key types, map keys that are themselves collections, or mutable builders.
Build leaves with Nil, Int, Float, String, StringClone, and Bool. Build keys with KNil, KInt, KFloat, KString, KStringClone, and KBool. Wrap collections with MapValue and VectorValue when they need to be stored inside another Value.
Collection updates never mutate the receiver. Instead, methods such as Map.Assoc, Map.Dissoc, Vector.Set, Vector.Append, and Vector.Pop return a new collection root while unchanged internal nodes remain shared with older versions. This makes it cheap to keep snapshots, implement undo stacks, build copy-on-write configuration trees, or derive request-local changes from a shared baseline.
Zero values are usable:
- Value{} is the nil value.
- Key{} is the nil key.
- Map{} is an empty map.
- Vector{} is an empty vector.
Persistent maps and vectors are safe for concurrent reads. Multiple goroutines may also derive separate new versions from the same root at the same time because published nodes are never modified.
Use typed accessors such as Value.Int64, Value.Map, Key.StringValue, Step.Key, and Step.Index to inspect values and paths. Accessors return a boolean instead of panicking on a kind mismatch. Use GetIn and SetIn to read and replace nested data through mixed map/vector paths.
Use FromJSON, Value.ToJSON, Map.ToJSON, Vector.ToJSON, or the standard encoding/json package to convert ordinary JSON documents. JSON objects decode as maps with string keys. Maps with non-string keys cannot be encoded as JSON objects.
Use NewReadOnlyMapConfig, NewReadOnlyVectorConfig, and NewReadOnlyConfig for read-only views over string-keyed map/vector trees that implement github.com/asciimoth/configer/configer.Config.
Use Value.Equal, Map.Equal, and Vector.Equal for deep semantic equality. Use Key.Equal, Step.Equal, and Kind.Equal for public key, path, and kind comparisons. Map.All and Vector.All return standard Go iterators over immediate entries. Map.Walk and Vector.Walk return standard Go iterators over nested map/vector trees. Use Value.Same for cheap scalar equality and collection identity checks. Same is intentionally not deep equality for maps and vectors. Map iteration order is unspecified and may vary across map lineages; vector iteration order is ascending index order.
Index ¶
- Variables
- type Key
- type Kind
- type Map
- func (m Map) All() iter.Seq2[Key, Value]
- func (m Map) Assoc(k Key, v Value) Map
- func (m Map) Dissoc(k Key) Map
- func (m Map) Equal(other Map) bool
- func (m Map) Get(k Key) (Value, bool)
- func (m Map) Len() int
- func (m Map) MarshalJSON() ([]byte, error)
- func (m Map) Range(fn func(Key, Value) bool)
- func (m Map) String() string
- func (m Map) ToJSON() ([]byte, error)
- func (m *Map) UnmarshalJSON(data []byte) error
- func (m Map) Walk() iter.Seq2[[]Step, Value]
- type ReadOnlyConfig
- func (c *ReadOnlyConfig) Delete(path configer.Path) error
- func (c *ReadOnlyConfig) Get(path configer.Path) (any, error)
- func (c *ReadOnlyConfig) IsReadOnly() bool
- func (c *ReadOnlyConfig) Lock()
- func (c *ReadOnlyConfig) RLock()
- func (c *ReadOnlyConfig) RUnlock()
- func (c *ReadOnlyConfig) ReadOnly() configer.Config
- func (c *ReadOnlyConfig) Set(path configer.Path, value any) error
- func (c *ReadOnlyConfig) Snapshot() any
- func (c *ReadOnlyConfig) Unlock()
- func (c *ReadOnlyConfig) View(path configer.Path) configer.Config
- type Step
- type Value
- func Bool(x bool) Value
- func Float(x float64) Value
- func FromJSON(data []byte) (Value, error)
- func GetIn(root Value, path ...Step) (Value, bool)
- func Int(x int64) Value
- func MapValue(m Map) Value
- func Nil() Value
- func SetIn(root Value, replacement Value, path ...Step) (Value, error)
- func String(x string) Value
- func StringClone(x string) Value
- func VectorValue(v Vector) Value
- func (v Value) BoolValue() (bool, bool)
- func (v Value) Equal(other Value) bool
- func (v Value) Float64() (float64, bool)
- func (v Value) GoString() string
- func (v Value) Int64() (int64, bool)
- func (v Value) Kind() Kind
- func (v Value) Map() (Map, bool)
- func (v Value) MarshalJSON() ([]byte, error)
- func (v Value) Same(other Value) bool
- func (v Value) StringValue() (string, bool)
- func (v Value) ToJSON() ([]byte, error)
- func (v *Value) UnmarshalJSON(data []byte) error
- func (v Value) Vector() (Vector, bool)
- type Vector
- func (v Vector) All() iter.Seq2[int, Value]
- func (v Vector) Append(value Value) Vector
- func (v Vector) Equal(other Vector) bool
- func (v Vector) Get(i int) (Value, bool)
- func (v Vector) Len() int
- func (v Vector) MarshalJSON() ([]byte, error)
- func (v Vector) Pop() (Vector, bool)
- func (v Vector) Range(fn func(int, Value) bool)
- func (v Vector) Set(i int, value Value) (Vector, bool)
- func (v Vector) String() string
- func (v Vector) ToJSON() ([]byte, error)
- func (v *Vector) UnmarshalJSON(data []byte) error
- func (v Vector) Walk() iter.Seq2[[]Step, Value]
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrInvalidPath = configer.ErrInvalidPath
ErrInvalidPath is returned when a path cannot be applied to the current tree.
var ErrNotFound = configer.ErrNotFound
ErrNotFound is returned when a path does not exist.
var ErrReadOnly = configer.ErrReadOnly
ErrReadOnly is returned by mutating operations on read-only configs.
Functions ¶
This section is empty.
Types ¶
type Key ¶
type Key struct {
// contains filtered or unexported fields
}
Key has the same compact physical representation as Value, but public key constructors only admit scalar map-key types.
Invariants:
- The zero Key is the nil key.
- Valid keys are nil, int64, float64, string, and bool only.
- Map and vector values cannot be represented as valid keys through the public API.
Usage:
- Build keys with KNil, KInt, KFloat, KString, KStringClone, and KBool.
- Use typed accessors when inspecting callback keys from Map.Range.
Security notes:
- KString stores string bytes without copying and can extend backing string lifetimes. KStringClone makes an exact-size retained copy.
- KFloat canonicalizes zeros and NaNs so untrusted NaN payloads cannot make entries unretrievable through non-reflexive key equality.
func KFloat ¶
KFloat returns a float64 map key with total, reflexive key semantics.
Signed zero is normalized to positive zero, and all NaN payloads are normalized to one canonical quiet NaN. Int(1) and Float(1) remain distinct key kinds.
func KStringClone ¶
KStringClone returns a string map key after copying x into an exact-size backing allocation.
func (Key) StringValue ¶
StringValue extracts a string key.
type Kind ¶
type Kind uint8
Kind identifies the closed set of value and key variants supported by this package.
Invariants:
- KindInvalid is never returned for values built through the public constructors.
- Map and vector kinds are valid Value kinds but invalid Key kinds.
Usage:
- Inspect a Value or Key with Kind before using typed accessors when the expected kind is not already known.
Security notes:
- Kind is metadata only. Use FromJSON or json.Unmarshal to validate external JSON before inspecting decoded values.
const ( // KindInvalid marks an impossible or internally corrupted representation. KindInvalid Kind = iota // KindNil is the nil leaf value and nil map key. KindNil // KindInt is an int64 leaf value or map key. KindInt // KindFloat is a float64 leaf value or map key. KindFloat // KindString is a string leaf value or map key. KindString // KindBool is a bool leaf value or map key. KindBool // KindMap is an immutable persistent map value. KindMap // KindVector is an immutable persistent vector value. KindVector )
type Map ¶
type Map struct {
// contains filtered or unexported fields
}
Map is an immutable persistent map from scalar Key to Value.
Invariants:
- The zero Map is empty.
- A non-empty small map stores alternating key/value slots in small and has root == nil.
- A large map stores a CHAMP hash trie in root; trie nodes are never mutated after publication.
- All derived versions share the same immutable hash state as their parent.
Usage:
- Use NewMap for a fresh empty map, then Assoc and Dissoc to derive new versions.
- Use Get for lookup, All or Range for unordered iteration, and Walk for recursive traversal.
- Map is a small header; copying it is cheap and does not copy entries.
Security notes:
- Maps use randomized maphash state per NewMap lineage to reduce predictable collision behavior. This is not a cryptographic hash or authorization boundary.
- Published nodes are immutable, making concurrent reads and concurrent derivation from a shared root race-free.
func NewMap ¶
func NewMap() Map
NewMap returns an empty map with a fresh randomized hash lineage.
The zero Map is also a valid empty map. NewMap is useful when independent map lineages should not share hash seed state.
func (Map) All ¶ added in v0.2.0
All returns an iterator over every key/value pair in m.
Iteration order is unspecified and may differ between map lineages and versions. The iterator stops early when yield returns false.
func (Map) Assoc ¶
Assoc returns a map containing k -> v.
The receiver is never modified. If k already maps to a Same value, Assoc returns the original map header and preserves representation identity.
func (Map) Dissoc ¶
Dissoc returns a map without k.
The receiver is never modified. If k is absent, Dissoc returns the original map header.
func (Map) MarshalJSON ¶
MarshalJSON implements json.Marshaler.
func (Map) Range ¶
Range calls fn for every entry until fn returns false.
Iteration order is unspecified and may differ between map lineages and versions. The callback must not rely on a stable order for serialization or signing.
func (Map) String ¶ added in v0.2.0
String returns m formatted as indented JSON.
Maps can be formatted only when every key is a string, because JSON object keys are strings. When m cannot be represented as JSON, String returns the serialization error text.
func (Map) ToJSON ¶
ToJSON encodes m as a JSON object.
Maps can be encoded only when every key is a string, because JSON object keys are strings. Non-finite Float values cannot be encoded as JSON numbers.
func (*Map) UnmarshalJSON ¶
UnmarshalJSON implements json.Unmarshaler.
func (Map) Walk ¶ added in v0.2.0
Walk returns a depth-first preorder iterator over every value reachable from entries in m.
Paths are relative to m and do not include an empty path for m itself. Each yielded path is a fresh slice that may be retained. Map entry order is unspecified, and vector entries are walked in ascending index order. Collection values are yielded before their descendants.
type ReadOnlyConfig ¶
type ReadOnlyConfig struct {
// contains filtered or unexported fields
}
ReadOnlyConfig is a Config wrapper around an immutable Value tree.
func NewReadOnlyConfig ¶
func NewReadOnlyConfig(root Value) *ReadOnlyConfig
NewReadOnlyConfig returns a read-only Config rooted at root.
func NewReadOnlyMapConfig ¶
func NewReadOnlyMapConfig(m Map) *ReadOnlyConfig
NewReadOnlyMapConfig returns a read-only Config rooted at m.
func NewReadOnlyVectorConfig ¶
func NewReadOnlyVectorConfig(v Vector) *ReadOnlyConfig
NewReadOnlyVectorConfig returns a read-only Config rooted at v.
func (*ReadOnlyConfig) Delete ¶
func (c *ReadOnlyConfig) Delete(path configer.Path) error
Delete returns ErrReadOnly.
func (*ReadOnlyConfig) Get ¶
func (c *ReadOnlyConfig) Get(path configer.Path) (any, error)
Get returns a deep copy of the value at path.
func (*ReadOnlyConfig) IsReadOnly ¶
func (c *ReadOnlyConfig) IsReadOnly() bool
IsReadOnly reports whether mutating operations are disabled.
func (*ReadOnlyConfig) ReadOnly ¶
func (c *ReadOnlyConfig) ReadOnly() configer.Config
ReadOnly returns a read-only view backed by the same lock.
func (*ReadOnlyConfig) Set ¶
func (c *ReadOnlyConfig) Set(path configer.Path, value any) error
Set returns ErrReadOnly.
func (*ReadOnlyConfig) Snapshot ¶
func (c *ReadOnlyConfig) Snapshot() any
Snapshot returns a deep copy of the view root.
type Step ¶
type Step struct {
// contains filtered or unexported fields
}
Step is one segment in a mixed map/vector path.
Invariants:
- Valid steps are created by MapKey or Index.
- Map steps contain a scalar Key.
- Index steps contain a vector index.
Usage:
- Pass steps to GetIn and SetIn to traverse nested immutable values.
- A final map key may be absent for SetIn, in which case it is associated. Intermediate map keys and all vector indices must already exist.
Security notes:
- Step carries no external capabilities. Error messages from SetIn describe only the failing container kind or index.
type Value ¶
type Value struct {
// contains filtered or unexported fields
}
Value is a compact immutable tagged value.
Invariants:
- The zero Value is the nil value.
- Values built by this package have one of the Kind constants and are never mutated after publication.
- Map and vector values hold pointers to immutable collection headers.
- String values hold a pointer to immutable Go string bytes and a byte length.
Usage:
- Build values with Nil, Int, Float, String, StringClone, Bool, MapValue, and VectorValue.
- Use typed accessors such as Int64 and Map to inspect a value.
- Use Same for cheap scalar equality and collection identity checks. Do not treat Same as deep equality.
Security notes:
- String stores the supplied string without copying. This can intentionally extend the lifetime of that string's backing bytes. Use StringClone when retaining a substring or sensitive input would be undesirable.
- The representation uses unsafe internally, but it does not store persistent pointers in uintptr, does not expose mutable aliases, and does not modify string bytes.
func Float ¶
Float returns a float64 leaf value.
Float values preserve their exact IEEE-754 bits. This means Value.Same distinguishes -0.0 from +0.0 and compares NaN values by stored bits. Float map keys deliberately use different, normalized semantics; see KFloat.
func FromJSON ¶
FromJSON decodes a JSON document into a Value.
JSON objects become maps with string keys, JSON arrays become vectors, and JSON null becomes Nil. Integer-looking numbers become Int values when they fit in int64; numbers with decimal points or exponents become Float values.
func GetIn ¶
GetIn follows a mixed map/vector path from root.
It returns false if any step cannot be applied or if an intermediate key or index is absent.
func MapValue ¶
MapValue wraps an immutable Map as a Value.
Re-wrapping the same Map can produce a distinct collection identity for Value.Same. No map entries or nodes are copied.
func SetIn ¶
SetIn returns a new nested root with path replaced by replacement.
If path is empty, SetIn returns replacement. The final map key may be absent; all intermediate containers must exist and vector indices must be valid. The original root is returned for semantic no-op updates.
Example ¶
package main
import (
"fmt"
"github.com/asciimoth/persist"
)
func main() {
item := persist.NewMap().
Assoc(persist.KString("name"), persist.String("Ada")).
Assoc(persist.KString("score"), persist.Int(10))
items := persist.NewVector(persist.MapValue(item))
doc := persist.MapValue(persist.NewMap().Assoc(persist.KString("items"), persist.VectorValue(items)))
updated, err := persist.SetIn(
doc,
persist.Int(11),
persist.MapKey(persist.KString("items")),
persist.Index(0),
persist.MapKey(persist.KString("score")),
)
if err != nil {
panic(err)
}
oldScore, _ := persist.GetIn(doc, persist.MapKey(persist.KString("items")), persist.Index(0), persist.MapKey(persist.KString("score")))
newScore, _ := persist.GetIn(updated, persist.MapKey(persist.KString("items")), persist.Index(0), persist.MapKey(persist.KString("score")))
oldN, _ := oldScore.Int64()
newN, _ := newScore.Int64()
fmt.Println(oldN, newN)
}
Output: 10 11
func String ¶
String returns a string leaf value without copying its bytes.
The value is immutable because Go strings are immutable, but the backing bytes remain live for as long as the Value remains live. Use StringClone when the caller wants an exact-size retained copy.
func StringClone ¶
StringClone returns a string leaf value after copying x into an exact-size backing allocation.
Use this when storing a long-lived value derived from a substring of a larger string, or when retaining the caller's original backing storage is not desirable. It does not erase or otherwise sanitize the original string.
func VectorValue ¶
VectorValue wraps an immutable Vector as a Value.
Re-wrapping the same Vector can produce a distinct collection identity for Value.Same. No vector elements or nodes are copied.
func (Value) Equal ¶
Equal reports semantic equality with other.
Scalar values use the same equality semantics as Same. Map and vector values compare recursively by content instead of wrapper identity.
func (Value) MarshalJSON ¶
MarshalJSON implements json.Marshaler.
func (Value) Same ¶
Same reports scalar equality and collection identity equality.
Same intentionally does not perform deep collection equality. Maps and vectors compare Same only when their wrapper identity is the same. Semantic no-op updates preserve identity, but independently constructed equal collections need not compare Same.
func (Value) StringValue ¶
StringValue extracts a string value.
func (Value) ToJSON ¶
ToJSON encodes v as JSON.
Maps can be encoded only when every key is a string, because JSON object keys are strings. Non-finite Float values cannot be encoded as JSON numbers.
func (*Value) UnmarshalJSON ¶
UnmarshalJSON implements json.Unmarshaler.
type Vector ¶
type Vector struct {
// contains filtered or unexported fields
}
Vector is an immutable persistent indexed sequence.
Invariants:
- The zero Vector is empty.
- tail contains the final 1..16 values when count > 0, or nil when empty.
- root contains complete 16-value leaves before the tail.
- shift is a multiple of vecBits and describes the root trie height.
- Root, branch, and leaf nodes are never mutated after publication.
Usage:
- Use NewVector or repeated Append to build a vector.
- Use Get, Set, Append, Pop, Len, All, Range, and Walk for indexed sequence work.
- Vector is optimized for append/pop at the end and random lookup/update. It intentionally does not provide insertion or removal in the middle.
Security notes:
- The implementation stores branch and leaf pointers as unsafe.Pointer so a single pointer type can represent both node kinds. Pointers remain visible to the garbage collector and are never converted to uintptr.
- Published nodes are immutable, so concurrent reads and concurrent derivation from a shared root are race-free.
func (Vector) All ¶ added in v0.2.0
All returns an iterator over every index/value pair in v.
Iteration is in ascending index order. The iterator stops early when yield returns false.
func (Vector) Append ¶
Append returns a vector with value added at the end.
The receiver is never modified.
func (Vector) MarshalJSON ¶
MarshalJSON implements json.Marshaler.
func (Vector) Pop ¶
Pop returns a vector without its final value.
The receiver is never modified. The bool result is false when the vector is empty.
func (Vector) Set ¶
Set returns a vector with index i replaced by value.
The receiver is never modified. The bool result is false for an invalid index. If the existing value is Same as value, Set returns the original vector header and true.
func (Vector) String ¶ added in v0.2.0
String returns v formatted as indented JSON.
When v cannot be represented as JSON, String returns the serialization error text.
func (Vector) ToJSON ¶
ToJSON encodes v as a JSON array.
Non-finite Float values cannot be encoded as JSON numbers.
func (*Vector) UnmarshalJSON ¶
UnmarshalJSON implements json.Unmarshaler.
func (Vector) Walk ¶ added in v0.2.0
Walk returns a depth-first preorder iterator over every value reachable from entries in v.
Paths are relative to v and do not include an empty path for v itself. Each yielded path is a fresh slice that may be retained. Vector entries are walked in ascending index order, and nested map entry order is unspecified. Collection values are yielded before their descendants.