Package

maps

Map utilities (merge with resolvers, invert, filter and map keys and values, counters, multimaps, diffs) and dynamic JSON values with paths, pointers and merge patches

0.1.1 MIT

Install

sspur add maps@^0.1.1

or in sspur.toml:

maps = { version = "^0.1.1" }

README

maps

Utilities for SSPUR's builtin Map[K, V] that the standard library leaves out (merging with a resolver, inverting, filtering and mapping keys and values, one-key updates, counters, multimaps and diffs), and dynamic JSON values: the standard library's json.decode[T] needs a static type, so maps adds JsonValue with a strict RFC 8259 parser, a serializer, dotted paths, RFC 6901 JSON Pointers, deep get/set/delete, deep merge, RFC 7386 merge patches, flattening and deep diffs.

Everything is pure. Functions that take a lambda are effect-polymorphic: they perform what the lambda performs. Maps are persistent values, so every function returns a new map.

Example

use maps.{counter, most_common, merge_with, invert_multi, diff_maps, parse_json, error_text, get_path, set_path, merge_patch, render, render_pretty, JsonValue, JStr}

fn main() -> Unit ! log
= do
  words = "the cat and the hat and the bat".split(" ")
  log(most_common(counter(words), 2).str)
  stock = merge_with(maps.from_pairs([("apples", 3), ("pears", 1)]), maps.from_pairs([("apples", 2), ("plums", 5)]), (k, a, b) => a + b)
  log(stock.str)
  log(invert_multi(maps.from_pairs([("ana", "admin"), ("bo", "dev"), ("cy", "admin")])).str)
  log(diff_maps(stock, stock.put("pears", 0).remove("plums")).str)
  match parse_json("\{\"user\": \{\"name\": \"Ana\", \"tags\": [\"a\", \"b\"]}, \"n\": 12345678901234567890}")
  | err(e) => log(error_text(e))
  | ok(doc) => do
    log(get_path(doc, "user.tags[1]").str)
    updated = set_path(doc, "user.address.city", JStr{s: "Pune"}).or(doc)
    patched = merge_patch(updated, parse_json("\{\"n\": null, \"user\": \{\"tags\": [\"z\"]}}").or(maps.JNull))
    log(render(patched))
    log(render_pretty(patched, 2))
  match parse_json("[1, 2,]")
  | err(e) => log(error_text(e))
  | ok(_) => log("parsed")

Output (the same natively and with --interp):

[("the", 3), ("and", 2)]
{"apples": 5, "pears": 1, "plums": 5}
{"admin": ["ana", "cy"], "dev": ["bo"]}
MapDiff{added: {}, removed: {"plums": 5}, changed: {"pears": (1, 0)}}
some(JStr{s: "b"})
{"user":{"address":{"city":"Pune"},"name":"Ana","tags":["z"]}}
{
  "user": {
    "address": {
      "city": "Pune"
    },
    "name": "Ana",
    "tags": [
      "z"
    ]
  }
}
line 1, column 7: trailing comma before ']'

API

Building maps

FunctionWhat it does
from_pairs(pairs)The map of pairs; a later pair for a key wins
from_pairs_with(pairs, resolve)Repeated keys combined with resolve(key, existing, incoming)
from_keys(keys, f)Each key mapped to f(key)
from_lists(keys, values)Keys zipped with values; extras ignored
index_by(xs, key)Each element under key(x); a later element wins
group_into(xs, key)Elements grouped by key(x) into a multimap, in input order
to_hash(m), from_hash(h)Convert to and from HashMap

Reading

FunctionWhat it does
get_or(m, k, default)The value, or the default
is_empty_map(m)Whether the map has no entries (Map has no builtin is_empty)
find_key(m, v)The smallest key whose value is v
find_entry(m, f)The first entry, in key order, with f(k, v)
keys_where(m, f)The keys whose value satisfies f
has_all(m, ks), has_any(m, ks)Whether all, or any, of the keys are present
min_by_value(m), max_by_value(m)The entry with the smallest or largest value (smallest key on ties)

Merging

FunctionWhat it does
merge(a, b)Both maps, b winning (Python's a | b)
merge_prefer_left(a, b)Both maps, a winning (lodash defaults)
merge_with(a, b, resolve)Shared keys combined with resolve(key, from_a, from_b)
merge_all(ms), merge_all_with(ms, resolve)A list of maps, left to right
intersect_keys(a, b), subtract_keys(a, b)Entries of a whose key is, or is not, in b

Inverting

FunctionWhat it does
invert(m)Value to key; on a shared value the largest key wins
invert_with(m, resolve)Value to key, combining keys with resolve(value, existing, next)
invert_multi(m)Value to all its keys, ascending

Filtering and transforming

FunctionWhat it does
filter_keys, filter_values, filter_entriesKeep the entries that pass
partition_entries(m, f)The entries that pass and the ones that don't
map_values(m, f), map_values_with_key(m, f)New values
map_keys(m, f), map_keys_with(m, f, resolve)New keys; collisions keep the later original key, or are resolved
map_entries(m, f)New pairs; a collision keeps the later entry
filter_map_values(m, f)Keep and transform in one pass (f gives Opt)
pick(m, ks), omit(m, ks)Only, or all but, the given keys
rename_key(m, from, to)Move one entry to a new key

Updating one key

FunctionWhat it does
adjust(m, k, f)f(v) at k if present
update(m, k, f)f(v) at k if present; none removes it
upsert(m, k, init, f)f(v) if present, else init (Java's Map.merge)
alter(m, k, f)f(current) decides: store or remove (Java's compute)
put_if_absent(m, k, v)Store only when missing

Counters

FunctionWhat it does
counter(xs)How many times each element occurs
increment(m, k, by)Add to one count (missing counts as 0)
add_counts(a, b), subtract_counts(a, b)Counter arithmetic (subtraction keeps positive counts)
most_common(m, n), least_common(m, n)The top or bottom n, ties in key order
count_total(m)The sum of the counts

Multimaps (Map[K, List[V]])

FunctionWhat it does
multi_add(m, k, v), multi_get(m, k)Append a value; the values at a key ([] when missing)
multi_remove(m, k, v)Remove every occurrence; an empty key goes
multi_from_pairs(pairs), multi_pairs(m)Build from pairs; flatten to pairs
multi_size(m)The number of values over all keys

Comparing and diffs

DefinitionWhat it does
same_keys(a, b), equal_by(a, b, eq), is_submap(a, b)Key and value comparisons
MapDiff[K, V]added, removed, and changed as (old, new)
diff_maps(old, new_map), diff_is_empty(d), apply_diff(m, d)Compute, test and apply a diff; apply_diff(a, diff_maps(a, b)) == b

JSON values

DefinitionWhat it does
JsonValueJNull, JBool{b}, JInt{i}, JNum{f}, JBig{digits}, JStr{s}, JArr{elems}, JObj{fields} (a Map[Str, JsonValue], so keys are sorted)
obj(pairs)An object from pairs
kind_of, is_null, as_bool, as_int, as_f64, as_str, as_array, as_objectInspect a value
JsonError, parse_json(text), parse_json_depth(text, max), default_max_depth(), error_text(e)Strict parsing with line and column
render(v), render_pretty(v, indent), quote_json(s)Serializing

Numbers: an integer that fits Int is JInt; a number with a fraction or exponent is JNum; a number that fits neither (an integer beyond 64 bits, or 1e400) is JBig with its exact text, so nothing is silently rounded or lost. -0 is JInt{i: 0} and -0.0 is JNum{f: -0.0}. A non-finite JNum renders as null. A repeated key in an object keeps its last value.

Paths and pointers

DefinitionWhat it does
PathSegKey{key} or At{pos}
parse_path(p), path_text(segs)Dotted paths: a.b[0].c, ["x.y"] for odd keys
parse_pointer(p), pointer_text(segs)RFC 6901 pointers: /a/b/0, ~0 and ~1 escapes
get_in, has_in, set_in, delete_in, update_inDeep access by steps
get_path, has_path, set_path, delete_pathThe same with a dotted path
get_pointer, has_pointer, set_pointer, delete_pointerThe same with a JSON Pointer

set_in creates missing objects (and arrays for an At step), replaces a null on the way, appends at the array's length (or with the pointer step -), and refuses to leave holes in arrays or to step into a scalar, with an error naming the path.

Merging, patches and diffs of JSON values

DefinitionWhat it does
deep_merge(a, b)Objects merge recursively, everything else from b
merge_patch(target, patch), make_merge_patch(source, target)RFC 7386 JSON Merge Patch, applied and generated
flatten_value(v), unflatten_value(m)To and from a map of dotted paths to leaves
Change, diff_values(a, b), apply_changes(v, changes)Deep diffs (Added, Removed, Replaced with paths), and applying them

Out of scope

  • HashMap versions of every function: convert with to_hash and from_hash, or use the same builtin methods (get put remove items), which both map types share.
  • Sorted-map navigation (floor, ceiling, ranges): the builtin FlatMap has lower_bound and between.
  • Insertion-ordered JSON objects: objects are Maps, sorted by key, as json.encode writes Map[Str, V]. Converting between JsonValue and typed records: use parse_json(json.encode(x)) and json.decode[T](render(v)).
  • JSON Patch (RFC 6902), JSONPath and JSON Schema; comments, trailing commas and other JSON5 extensions (the parser is strict on purpose); streaming parsing of huge documents.
  • Array diffs by longest common subsequence: diff_values compares arrays position by position.

Install

sspur add maps

MIT licensed; see LICENSE.

API

from_pairs fn

fn from_pairs[K, V](pairs: List[(K, V)]) -> Map[K, V]

The map of pairs; a later pair for a key replaces an earlier one, as in a series of puts.

from_pairs_with fn

fn from_pairs_with[K, V, e](pairs: List[(K, V)], resolve: (K, V, V) -> V ! e) -> Map[K, V] ! e

The map of pairs, combining the values of a repeated key with resolve(key, existing, incoming).

from_keys fn

fn from_keys[K, V, e](keys: List[K], f: K -> V ! e) -> Map[K, V] ! e

The map from each of keys to f(key).

from_lists fn

fn from_lists[K, V](keys: List[K], values: List[V]) -> Map[K, V]

The map from keys[i] to values[i]; extra keys or values are ignored, and a repeated key takes its last value.

index_by fn

fn index_by[T, K, e](xs: List[T], key: T -> K ! e) -> Map[K, T] ! e

The map from key(x) to x for each element; a later element with the same key wins.

group_into fn

fn group_into[T, K, e](xs: List[T], key: T -> K ! e) -> Map[K, List[T]] ! e

The elements of xs grouped by key(x), each group in the order of xs.

to_hash fn

fn to_hash[K, V](m: Map[K, V]) -> HashMap[K, V]

The same entries as a HashMap.

from_hash fn

fn from_hash[K, V](h: HashMap[K, V]) -> Map[K, V]

The same entries as an ordered Map.

get_or fn

fn get_or[K, V](m: Map[K, V], k: K, default: V) -> V

The value for k, or default when k is missing.

is_empty_map fn

fn is_empty_map[K, V](m: Map[K, V]) -> Bool

Whether m has no entries.

find_key fn

fn find_key[K, V](m: Map[K, V], v: V) -> Opt[K]

The smallest key whose value equals v.

find_entry fn

fn find_entry[K, V, e](m: Map[K, V], f: (K, V) -> Bool ! e) -> Opt[(K, V)] ! e

The first entry, in key order, for which f(key, value) holds.

keys_where fn

fn keys_where[K, V, e](m: Map[K, V], f: V -> Bool ! e) -> List[K] ! e

The keys, in order, whose value satisfies f.

has_all fn

fn has_all[K, V](m: Map[K, V], ks: List[K]) -> Bool

Whether every key of ks is in m.

has_any fn

fn has_any[K, V](m: Map[K, V], ks: List[K]) -> Bool

Whether some key of ks is in m.

min_by_value fn

fn min_by_value[K, V: Ord](m: Map[K, V]) -> Opt[(K, V)]

The entry with the smallest value (the smallest key among equal values).

max_by_value fn

fn max_by_value[K, V: Ord](m: Map[K, V]) -> Opt[(K, V)]

The entry with the largest value (the smallest key among equal values).

merge fn

fn merge[K, V](a: Map[K, V], b: Map[K, V]) -> Map[K, V]

The entries of both maps; for a key in both, the value from b wins (like Python's a | b).

merge_prefer_left fn

fn merge_prefer_left[K, V](a: Map[K, V], b: Map[K, V]) -> Map[K, V]

The entries of both maps; for a key in both, the value from a wins (lodash defaults).

merge_with fn

fn merge_with[K, V, e](a: Map[K, V], b: Map[K, V], resolve: (K, V, V) -> V ! e) -> Map[K, V] ! e

The entries of both maps; for a key in both, the value is resolve(key, from_a, from_b).

merge_all fn

fn merge_all[K, V](ms: List[Map[K, V]]) -> Map[K, V]

The entries of all maps, later maps winning; the empty map for an empty list.

merge_all_with fn

fn merge_all_with[K, V, e](ms: List[Map[K, V]], resolve: (K, V, V) -> V ! e) -> Map[K, V] ! e

The entries of all maps, folding repeated keys from left to right with resolve(key, acc, next).

intersect_keys fn

fn intersect_keys[K, V, W](a: Map[K, V], b: Map[K, W]) -> Map[K, V]

The entries of a whose key is also in b, with a's values.

subtract_keys fn

fn subtract_keys[K, V, W](a: Map[K, V], b: Map[K, W]) -> Map[K, V]

The entries of a whose key is not in b.

invert fn

fn invert[K, V](m: Map[K, V]) -> Map[V, K]

The map from each value to its key. When several keys share a value the largest key wins, because entries are visited in key order and later ones replace earlier ones.

invert_with fn

fn invert_with[K, V, e](m: Map[K, V], resolve: (V, K, K) -> K ! e) -> Map[V, K] ! e

The map from each value to its key, combining keys that share a value with resolve(value, existing_key, next_key) (keys arrive in ascending order).

invert_multi fn

fn invert_multi[K, V](m: Map[K, V]) -> Map[V, List[K]]

The map from each value to all keys that have it, in ascending key order.

filter_keys fn

fn filter_keys[K, V, e](m: Map[K, V], f: K -> Bool ! e) -> Map[K, V] ! e

The entries whose key satisfies f.

filter_values fn

fn filter_values[K, V, e](m: Map[K, V], f: V -> Bool ! e) -> Map[K, V] ! e

The entries whose value satisfies f.

filter_entries fn

fn filter_entries[K, V, e](m: Map[K, V], f: (K, V) -> Bool ! e) -> Map[K, V] ! e

The entries for which f(key, value) holds.

partition_entries fn

fn partition_entries[K, V, e](m: Map[K, V], f: (K, V) -> Bool ! e) -> (Map[K, V], Map[K, V]) ! e

The entries that satisfy f(key, value) and the ones that don't.

map_values fn

fn map_values[K, V, W, e](m: Map[K, V], f: V -> W ! e) -> Map[K, W] ! e

Every value replaced by f(value).

map_values_with_key fn

fn map_values_with_key[K, V, W, e](m: Map[K, V], f: (K, V) -> W ! e) -> Map[K, W] ! e

Every value replaced by f(key, value).

map_keys fn

fn map_keys[K, V, K2, e](m: Map[K, V], f: K -> K2 ! e) -> Map[K2, V] ! e

Every key replaced by f(key). When two keys map to the same new key, the value of the larger original key wins; use map_keys_with to choose.

map_keys_with fn

fn map_keys_with[K, V, K2, e](m: Map[K, V], f: K -> K2 ! e, resolve: (K2, V, V) -> V ! e) -> Map[K2, V] ! e

Every key replaced by f(key), combining colliding values with resolve(new_key, existing, incoming) in ascending order of the original keys.

map_entries fn

fn map_entries[K, V, K2, W, e](m: Map[K, V], f: (K, V) -> (K2, W) ! e) -> Map[K2, W] ! e

Every entry replaced by the pair f(key, value); on a key collision the later entry, in the original key order, wins.

filter_map_values fn

fn filter_map_values[K, V, W, e](m: Map[K, V], f: (K, V) -> Opt[W] ! e) -> Map[K, W] ! e

The entries for which f(key, value) gives some(w), with the value w.

pick fn

fn pick[K, V](m: Map[K, V], ks: List[K]) -> Map[K, V]

Only the entries whose key is in ks.

omit fn

fn omit[K, V](m: Map[K, V], ks: List[K]) -> Map[K, V]

The entries whose key is not in ks.

rename_key fn

fn rename_key[K, V](m: Map[K, V], from: K, to: K) -> Map[K, V]

The map with the entry of from moved to the key to, replacing any entry already at to. Unchanged when from is missing or equal to to.

adjust fn

fn adjust[K, V, e](m: Map[K, V], k: K, f: V -> V ! e) -> Map[K, V] ! e

The value at k replaced by f(value); unchanged when k is missing.

update fn

fn update[K, V, e](m: Map[K, V], k: K, f: V -> Opt[V] ! e) -> Map[K, V] ! e

The value at k replaced by f(value), or removed when f gives none; unchanged when k is missing.

upsert fn

fn upsert[K, V, e](m: Map[K, V], k: K, init: V, f: V -> V ! e) -> Map[K, V] ! e

f(value) at k when k is present, else init (Java's Map.merge).

alter fn

fn alter[K, V, e](m: Map[K, V], k: K, f: Opt[V] -> Opt[V] ! e) -> Map[K, V] ! e

The entry at k decided by f(current): some(v) stores v, none removes it (Haskell's alter, Java's compute).

put_if_absent fn

fn put_if_absent[K, V](m: Map[K, V], k: K, v: V) -> Map[K, V]

The map with v at k only when k is missing (Python's setdefault, without the read).

counter fn

fn counter[T](xs: List[T]) -> Map[T, Int]

How many times each element occurs.

increment fn

fn increment[K](m: Map[K, Int], k: K, by: Int) -> Map[K, Int]

The count at k raised by by (which may be negative); a missing key counts as 0, and a key whose count reaches 0 stays in the map.

add_counts fn

fn add_counts[K](a: Map[K, Int], b: Map[K, Int]) -> Map[K, Int]

The counts of both counters added key by key.

subtract_counts fn

fn subtract_counts[K](a: Map[K, Int], b: Map[K, Int]) -> Map[K, Int]

The counts of a minus those of b, keeping only keys whose result is positive (Python's Counter - Counter).

most_common fn

fn most_common[K](m: Map[K, Int], n: Int where _ >= 0) -> List[(K, Int)]

The n entries with the highest counts, highest first; equal counts are in key order. Fewer when the counter is smaller; n 0 gives [].

least_common fn

fn least_common[K](m: Map[K, Int], n: Int where _ >= 0) -> List[(K, Int)]

The n entries with the lowest counts, lowest first; equal counts are in key order.

count_total fn

fn count_total[K](m: Map[K, Int]) -> Int

The sum of all counts.

multi_add fn

fn multi_add[K, V](m: Map[K, List[V]], k: K, v: V) -> Map[K, List[V]]

The multimap with v appended to the list at k.

multi_get fn

fn multi_get[K, V](m: Map[K, List[V]], k: K) -> List[V]

The values at k, [] when missing.

multi_remove fn

fn multi_remove[K, V](m: Map[K, List[V]], k: K, v: V) -> Map[K, List[V]]

The multimap with every occurrence of v removed from the list at k; the key goes when its list becomes empty.

multi_from_pairs fn

fn multi_from_pairs[K, V](pairs: List[(K, V)]) -> Map[K, List[V]]

The multimap of pairs, each key's values in the order given.

multi_pairs fn

fn multi_pairs[K, V](m: Map[K, List[V]]) -> List[(K, V)]

Every (key, value) pair of the multimap, in key order and then list order.

multi_size fn

fn multi_size[K, V](m: Map[K, List[V]]) -> Int

The number of values in the multimap, over all keys.

same_keys fn

fn same_keys[K, V, W](a: Map[K, V], b: Map[K, W]) -> Bool

Whether both maps have exactly the same keys.

equal_by fn

fn equal_by[K, V, W, e](a: Map[K, V], b: Map[K, W], eq: (V, W) -> Bool ! e) -> Bool ! e

Whether both maps have the same keys and eq holds for the values at each key.

is_submap fn

fn is_submap[K, V](a: Map[K, V], b: Map[K, V]) -> Bool

Whether every entry of a is also in b with an equal value.

MapDiff type

pub type MapDiff[K, V] = {added: Map[K, V], removed: Map[K, V], changed: Map[K, (V, V)]} derive Eq, Show

The difference between two maps: entries only in the new map, entries only in the old one, and keys in both whose values differ, as (old, new).

diff_maps fn

fn diff_maps[K, V](old: Map[K, V], new_map: Map[K, V]) -> MapDiff[K, V]

What changed from old to new_map.

diff_is_empty fn

fn diff_is_empty[K, V](d: MapDiff[K, V]) -> Bool

Whether a diff has no changes.

apply_diff fn

fn apply_diff[K, V](m: Map[K, V], d: MapDiff[K, V]) -> Map[K, V]

m with a diff applied: removed keys go, added and changed keys take their new values. So apply_diff(a, diff_maps(a, b)) == b. Keys of removed and changed are applied whatever their current value is.

JsonValue type

pub type JsonValue = JNull | JBool{b: Bool} | JInt{i: Int} | JNum{f: F64} | JBig{digits: Str} | JStr{s: Str} | JArr{elems: List[JsonValue]} | JObj{fields: Map[Str, JsonValue]} derive Eq, Show

A JSON value. Integers that fit Int are JInt; numbers with a fraction or an exponent are JNum; a number that fits neither (an integer beyond 64 bits, or a float that overflows) is JBig, which keeps its exact text. Object keys are kept sorted (a Map), so objects compare and print the same whatever order their keys were written in. The functions below recurse on nesting and are tested to 4096 levels in both tiers.

JsonError type

pub type JsonError = {message: Str, offset: Int, line: Int, column: Int} derive Eq, Show

Where and why JSON text failed to parse: offset counts characters from 0, line and column count from 1.

PathSeg type

pub type PathSeg = Key{key: Str} | At{pos: Int where _ >= 0} derive Eq, Ord, Show

One step of a path: an object key, or an array position.

Change type

pub type Change = Added{path: List[PathSeg], value: JsonValue} | Removed{path: List[PathSeg], value: JsonValue} | Replaced{path: List[PathSeg], before: JsonValue, after: JsonValue} derive Eq, Show

A difference found by diff_values, at a path from the root.

default_max_depth fn

fn default_max_depth() -> Int

The deepest nesting of arrays and objects parse_json accepts.

obj fn

fn obj(pairs: List[(Str, JsonValue)]) -> JsonValue

An object from key-value pairs; a repeated key takes its last value.

kind_of fn

fn kind_of(v: JsonValue) -> Str

The kind of a value: "null", "boolean", "number" (JInt, JNum and JBig), "string", "array" or "object".

is_null fn

fn is_null(v: JsonValue) -> Bool

Whether the value is null.

as_bool fn

fn as_bool(v: JsonValue) -> Opt[Bool]

The boolean, if the value is one.

as_int fn

fn as_int(v: JsonValue) -> Opt[Int]

The integer, if the value is a JInt, or a JNum with no fraction within the Int range.

as_f64 fn

fn as_f64(v: JsonValue) -> Opt[F64]

The number as an F64, for any number; a JBig integer is rounded and an overflowing one is none.

as_str fn

fn as_str(v: JsonValue) -> Opt[Str]

The string, if the value is one.

as_array fn

fn as_array(v: JsonValue) -> Opt[List[JsonValue]]

The elements, if the value is an array.

as_object fn

fn as_object(v: JsonValue) -> Opt[Map[Str, JsonValue]]

The fields, if the value is an object.

parse_json fn

fn parse_json(text: Str) -> Res[JsonValue, JsonError]

The value of JSON text, strictly as RFC 8259 says: one value with optional whitespace around it, no comments, no trailing commas, no single quotes, no leading zeros or +, no NaN, and strings with valid escapes and no raw control characters; a \u escape of a lone surrogate is an error. A repeated object key keeps its last value. Nesting deeper than default_max_depth() is an error.

parse_json_depth fn

fn parse_json_depth(text: Str, max_depth: Int where _ >= 0 and _ <= 4096) -> Res[JsonValue, JsonError]

parse_json with a nesting limit: arrays and objects may nest max_depth deep (0 allows only scalars).

error_text fn

fn error_text(e: JsonError) -> Str

The error as one line: "line 1, column 5: expected ':' after an object key".

render fn

fn render(v: JsonValue) -> Str

The value as compact JSON text: no spaces, object keys in sorted order, strings escaped as RFC 8259 requires (", \, and control characters; other characters are written as they are). A JNum that is not finite is written null; a JBig is written as its text.

render_pretty fn

fn render_pretty(v: JsonValue, indent: Int where _ >= 0 and _ <= 16) -> Str

The value as indented JSON text: one element or field per line, indent spaces per level, ": " after keys, and [] and {} for empty containers. indent 0 still breaks lines.

quote_json fn

fn quote_json(s: Str) -> Str

The string as a JSON string literal, with its quotes.

parse_path fn

fn parse_path(path: Str) -> Res[List[PathSeg], Str]

The steps of a dotted path such as a.b[0].c or users[2]["first name"]: names separated by dots, [n] for an array position, and ["..."] (with \" and \\ escapes) for a key that has dots, brackets or quotes in it. "" is the root (no steps). A name can't be empty (a..b is an error) and a position can't be negative or have a leading zero.

path_text fn

fn path_text(segs: List[PathSeg]) -> Str

A path as text that parse_path reads back: plain names joined with dots, [n] for positions, and ["..."] for empty keys and keys with ., [, ] or ".

parse_pointer fn

fn parse_pointer(ptr: Str) -> Res[List[PathSeg], Str]

The steps of an RFC 6901 JSON Pointer such as /a/b/0: "" is the whole document, every other pointer starts with /, and ~1 and ~0 in a step stand for / and ~. Every step is a Key; on an array a key that is a position (0, 12, not 01) indexes it, and - means the end (for set_in).

pointer_text fn

fn pointer_text(segs: List[PathSeg]) -> Str

A path as an RFC 6901 JSON Pointer, escaping ~ as ~0 and / as ~1.

get_in fn

fn get_in(v: JsonValue, segs: List[PathSeg]) -> Opt[JsonValue]

The value at a path, or none when a step is missing. A Key step reads an object field, or an array element when the key is a position; an At step reads only array elements.

has_in fn

fn has_in(v: JsonValue, segs: List[PathSeg]) -> Bool

Whether there is a value at the path (a null there counts).

set_in fn

fn set_in(v: JsonValue, segs: List[PathSeg], x: JsonValue) -> Res[JsonValue, Str]

The value with x stored at a path, or why it can't be. Missing objects along the way are created (and a null is replaced by one), as an object for a Key step and an array for an At step. On an array the position must be at most its length (the length, or the key -, appends), so arrays never get holes. A step into a boolean, number or string, a key that is not a position on an array, or an At step on an object is an error that names the path.

delete_in fn

fn delete_in(v: JsonValue, segs: List[PathSeg]) -> JsonValue

The value without whatever is at a path: an object field is removed and an array element is taken out (later elements move down). Unchanged when the path is missing or segs is empty.

update_in fn

fn update_in[e](v: JsonValue, segs: List[PathSeg], f: JsonValue -> JsonValue ! e) -> JsonValue ! e

The value with the value at a path replaced by f(old); unchanged when the path is missing.

get_path fn

fn get_path(v: JsonValue, path: Str) -> Opt[JsonValue]

get_in with a dotted path; none also when the path doesn't parse.

has_path fn

fn has_path(v: JsonValue, path: Str) -> Bool

has_in with a dotted path; false also when the path doesn't parse.

set_path fn

fn set_path(v: JsonValue, path: Str, x: JsonValue) -> Res[JsonValue, Str]

set_in with a dotted path; a path that doesn't parse is an error too.

delete_path fn

fn delete_path(v: JsonValue, path: Str) -> Res[JsonValue, Str]

delete_in with a dotted path; a path that doesn't parse is an error.

get_pointer fn

fn get_pointer(v: JsonValue, ptr: Str) -> Opt[JsonValue]

get_in with a JSON Pointer; none also when the pointer doesn't parse.

has_pointer fn

fn has_pointer(v: JsonValue, ptr: Str) -> Bool

has_in with a JSON Pointer.

set_pointer fn

fn set_pointer(v: JsonValue, ptr: Str, x: JsonValue) -> Res[JsonValue, Str]

set_in with a JSON Pointer; - as an array step appends.

delete_pointer fn

fn delete_pointer(v: JsonValue, ptr: Str) -> Res[JsonValue, Str]

delete_in with a JSON Pointer.

deep_merge fn

fn deep_merge(a: JsonValue, b: JsonValue) -> JsonValue

b merged into a: where both are objects their fields merge recursively, anywhere else (arrays included) the value from b replaces the one from a.

merge_patch fn

fn merge_patch(target: JsonValue, patch: JsonValue) -> JsonValue

target with an RFC 7386 JSON Merge Patch applied: an object patch sets each of its fields recursively and removes the fields it sets to null; any other patch replaces the target.

make_merge_patch fn

fn make_merge_patch(source: JsonValue, target: JsonValue) -> JsonValue

The RFC 7386 merge patch that turns source into target, so merge_patch(source, make_merge_patch(source, target)) == target whenever target has no null inside an object (merge patches can't set a field to null). Unchanged fields are left out; arrays are replaced whole.

flatten_value fn

fn flatten_value(v: JsonValue) -> Map[Str, JsonValue]

The leaves of a value keyed by their path_text: scalars, and empty arrays and objects (so unflatten_value can restore them). A scalar or empty root is the single key "".

unflatten_value fn

fn unflatten_value(m: Map[Str, JsonValue]) -> Res[JsonValue, Str]

The value whose leaves are m, the inverse of flatten_value: each key is parsed with parse_path and set with set_in, in path order (an empty map gives an empty object). Errors when a key doesn't parse, when two keys conflict (a and a.b), or when array positions skip one.

diff_values fn

fn diff_values(a: JsonValue, b: JsonValue) -> List[Change]

The differences from a to b, in path order: fields and trailing array elements only in b are Added, only in a Removed, and other values that differ Replaced (a change of kind, or of a scalar). Arrays are compared position by position, so an insertion shows as replacements plus an addition. Equal values give [].

apply_changes fn

fn apply_changes(v: JsonValue, changes: List[Change]) -> Res[JsonValue, Str]

v with changes applied, as diff_values produces them: Replaced and Added in order, then Removed from the last to the first so array positions stay valid. So apply_changes(a, diff_values(a, b)) == ok(b). Errors when a path can't be set.

Versions

VersionPublishedCommitSourceEffects
0.1.1 2026-10-1845f6af9963snapshot
0.1.0 2026-10-16a832998d6fsnapshot