encoding/value
A dynamic value for data whose shape you do not know in advance; the shared language of every format in this group.
import encoding.value
Value is the dynamic data type shared by the encoding packages. One Value is a null, a bool, a 64-bit int, a float, a string, an array of values or an object mapping strings to values, and its kind field says which. encoding/json, encoding/yaml, encoding/toml, encoding/msgpack and others decode into it and encode from it, and encoding/serde converts between formats through it.
Reach for a Value when the shape of the data is not known when you write the program: a webhook payload, a config with optional sections, a document you only pass along. When the shape is fixed, decoding straight into a struct (for example with json.Unmarshal) is shorter and checks the types for you.
The functions in four groups
| Job | Functions |
|---|---|
| Make a value | ValueNull, ValueBool, ValueInt, ValueFloat, ValueString, NewValueArray, NewValueObject |
| Ask the kind | IsNull, IsBool, IsInt, IsFloat, IsNumber, IsString, IsArray, IsObject |
| Read | AsBool, AsInt, AsFloat, AsString, ArrayLen, ArrayGet, ObjectGet, ObjectGetOk, ObjectHas, ObjectKeys |
| Change | ArrayPush, ArraySet, ObjectSet, ObjectDelete, ValueClone, ValueFree |
What trips people up
- Reading never checks the kind.
AsStringof a number is""andAsIntof a string is 0. AskIsStringorIsNumberfirst when the data comes from outside. - Missing is null, all the way down.
ObjectGeton a missing key, or on something that is not an object, returns null, so a chain likeObjectGet(ObjectGet(doc, "a"), "b")is safe.ArrayGetis not: an index pastArrayLenstops the program. - Copies share parts. A copy of an object shares its map, so
ObjectSetthrough the copy changes the original. A copy of an array shares its elements but not its length, soArrayPushon an array you got fromObjectGetis lost until youObjectSetit back. UseValueClonefor a real copy. - Key order depends on how the object was built.
ObjectKeysreturns keys in the order they were added withObjectSet, and a decoded JSON document in the order of its text. An object made from a map byValueObjectfollows that map's own order. Sort the keys, or encode withsort_keys, when output must be alphabetical.
Guide: walk a whole document
A recursive function over Value handles any shape: for an object, visit each key; for an array, each index; anything else is a leaf. This one prints the path of every leaf, which is handy for logging a webhook payload or comparing two documents line by line.
import encoding.value
import encoding.json
import encoding.encerr
import sort
import str
func walk(v: value.Value, path: str):
if value.IsObject(v):
keys := value.ObjectKeys(v)
sort.QuickSort(keys)
each k in keys:
walk(value.ObjectGet(v, k), path + "." + k)
end
else value.IsArray(v):
repeat value.ArrayLen(v) in i:
walk(value.ArrayGet(v, i), path + "[" + str.FromInt(i) + "]")
end
else:
println path, "=", value.ToDebugString(v)
end
end
func main:
mut e := encerr.NoError()
doc := json.Decode(`{"order": 1042, "customer": {"name": "Sara", "vip": true}, "lines": [{"sku": "BK-01", "qty": 2}, {"sku": "PN-07", "qty": 1}], "note": null}`, e)
walk(doc, "$")
endObjectKeys gives the keys in the order they were added, so a decoded document keeps the order of its text. This program sorts them with sort.QuickSort to print them alphabetically whatever order the input had. ToDebugString shows each leaf together with its kind.
Guide: build a payload with optional fields
A struct always writes all its fields. When some fields should only appear when they have a value, as many HTTP APIs expect, build a Value object instead and set only what you have. json.Encode with sort_keys prints it in a stable order.
import encoding.value
import encoding.json
func payload(name: str, email: str, age: int, tags: Vector<str>): value.Value:
mut v := value.NewValueObject()
value.ObjectSet(v, "name", value.ValueString(name))
if email != "":
value.ObjectSet(v, "email", value.ValueString(email))
end
if age > 0:
value.ObjectSet(v, "age", value.ValueInt(age))
end
if tags.len() > 0:
mut arr := value.NewValueArray()
each t in tags:
value.ArrayPush(arr, value.ValueString(t))
end
value.ObjectSet(v, "tags", arr)
end
ret v
end
func main:
opts := json.JSONOptions { sort_keys = true }
mut tags := Vector {} as Vector<str>
println json.Encode(payload("Sara", "", 0, tags), opts)
tags.push("beta")
tags.push("vip")
println json.Encode(payload("Reza", "reza@example.com", 41, tags), opts)
endGuide: change a nested field
To change something deep in a decoded document, take the inner part with ObjectGet, change it, and store it back with ObjectSet. For objects the store is not strictly needed, because the map is shared, but for arrays it is: the program below shows the new order line missing until the array is set back.
import encoding.value
import encoding.json
import encoding.encerr
func line(sku: str, qty: int): value.Value:
mut l := value.NewValueObject()
value.ObjectSet(l, "sku", value.ValueString(sku))
value.ObjectSet(l, "qty", value.ValueInt(qty))
ret l
end
func main:
mut e := encerr.NoError()
mut order := json.Decode(`{"id": 1042, "customer": {"name": "Sara"}, "lines": [{"sku": "BK-01", "qty": 2}]}`, e)
mut customer := value.ObjectGet(order, "customer")
value.ObjectSet(customer, "tier", value.ValueString("gold"))
mut lines := value.ObjectGet(order, "lines")
value.ArrayPush(lines, line("PN-07", 1))
println "before ObjectSet:", value.ArrayLen(value.ObjectGet(order, "lines")), "line(s)"
value.ObjectSet(order, "lines", lines)
println "after ObjectSet: ", value.ArrayLen(value.ObjectGet(order, "lines")), "line(s)"
println json.Encode(order, json.JSONOptions { sort_keys = true })
city := value.ObjectGet(value.ObjectGet(order, "shipping"), "city")
println "shipping city given:", not value.IsNull(city), "[" + value.AsString(city) + "]"
endAlways writing the changed part back with ObjectSet makes the code correct for both kinds, and keeps working if the representation changes. The last line reads a field of a shipping object that does not exist: the chain of ObjectGet calls returns null and AsString gives an empty string, with no crash.
Guide: deep merge of settings
Settings often come in layers: built-in defaults, then a file per site. A deep merge keeps every default the site file does not mention and replaces only what it does, one level at a time. Arrays and plain values in the override win as a whole.
import encoding.value
import encoding.json
import encoding.encerr
func merge(base: value.Value, over: value.Value): value.Value:
if not value.IsObject(base) or not value.IsObject(over):
ret value.ValueClone(over)
end
mut out := value.ValueClone(base)
each k in value.ObjectKeys(over):
o := value.ObjectGet(over, k)
if value.ObjectHas(out, k):
value.ObjectSet(out, k, merge(value.ObjectGet(out, k), o))
else:
value.ObjectSet(out, k, value.ValueClone(o))
end
end
ret out
end
func main:
mut e := encerr.NoError()
defaults := json.Decode(`{"server": {"host": "0.0.0.0", "port": 8080}, "log": {"level": "info", "json": false}, "plugins": ["auth"]}`, e)
site := json.Decode(`{"server": {"port": 9000}, "log": {"level": "debug"}, "plugins": ["auth", "cache"]}`, e)
cfg := merge(defaults, site)
println json.Encode(cfg, json.JSONOptions { indent = " ", sort_keys = true })
println "defaults untouched:", value.AsInt(value.ObjectGet(value.ObjectGet(defaults, "server"), "port"))
endThe function clones what it keeps, so defaults is the same afterwards. Without the clones, the result would share its objects with the inputs, as the next section shows.
Guide: copies, conversions and missing keys
Assigning a Value to a new variable does not copy its contents: an object's fields live in a shared map, so a change through the copy shows up in the original. Call ValueClone when you need an independent copy, and ValueFree when you are done with it. Free only values nothing else shares, since freeing a plain copy also frees the original's map.
import encoding.value
func main:
mut a := value.NewValueObject()
value.ObjectSet(a, "n", value.ValueInt(1))
mut shared := a
value.ObjectSet(shared, "n", value.ValueInt(2))
println "after editing a plain copy:", value.AsInt(value.ObjectGet(a, "n"))
mut own := value.ValueClone(a)
value.ObjectSet(own, "n", value.ValueInt(3))
println "after editing a clone:", value.AsInt(value.ObjectGet(a, "n"))
println "AsInt of 2.9:", value.AsInt(value.ValueFloat(2.9))
println "AsFloat of 3:", value.AsFloat(value.ValueInt(3))
println "AsInt of \"5\":", value.AsInt(value.ValueString("5"))
println "1 equals 1.0:", value.Equal(value.ValueInt(1), value.ValueFloat(1.0))
mut found := false
_ := value.ObjectGetOk(a, "missing", found)
println "missing key found:", found
value.ValueFree(own)
endAsIntcuts a float toward zero andAsFloatwidens an int, but neither parses text: a string gives 0. Check the kind first withIsInt,IsNumberorIsString.Equalcompares kinds too, so the integer 1 and the float 1.0 are different values.ObjectGeton a missing key gives a null value;ObjectGetOkalso tells you whether the key was there.
Types
Value
struct Value| Name | Type | Description |
|---|---|---|
kind | int | The kind of the value; one of the Kind constants. |
b | bool = false | The boolean value. |
i | i64 = 0 | The whole number value. |
n | f64 = 0 | The decimal value. |
s | str = "" | The string value. |
arr | Vector | The elements of an array. |
obj | HashMap | The keys and values of an object. |
order | i64 = 0 | An internal handle to the list that records the order in which keys were added to an object with ObjectSet. ObjectDelete keeps it up to date. Do not use it directly; ObjectKeys reads it. |
import encoding.value
func main:
mut user := value.NewValueObject()
value.ObjectSet(user, "name", value.ValueString("Narges"))
value.ObjectSet(user, "age", value.ValueInt(34))
value.ObjectSet(user, "height", value.ValueFloat(1.68))
value.ObjectSet(user, "admin", value.ValueBool(false))
value.ObjectSet(user, "phone", value.ValueNull())
mut languages := value.NewValueArray()
value.ArrayPush(languages, value.ValueString("fa"))
value.ArrayPush(languages, value.ValueString("en"))
value.ArraySet(languages, 1, value.ValueString("ar"))
value.ObjectSet(user, "langs", languages)
println value.ToDebugString(user)
println value.ObjectLen(user), value.ObjectHas(user, "age"), value.ObjectKeys(user)
println value.AsString(value.ObjectGet(user, "name")), value.AsInt(value.ObjectGet(user, "age")), value.AsFloat(value.ObjectGet(user, "height")), value.AsBool(value.ObjectGet(user, "admin"))
langs := value.ObjectGet(user, "langs")
println value.IsArray(langs), value.ArrayLen(langs), value.AsString(value.ArrayGet(langs, 1))
mut present := false
_ := value.ObjectGetOk(user, "email", present)
println present
phone := value.ObjectGet(user, "phone")
println value.IsNull(phone), value.IsBool(phone), value.IsInt(value.ValueInt(1)), value.IsFloat(value.ValueFloat(1.0)), value.IsNumber(value.ValueInt(1))
println value.IsString(value.ValueString("")), value.IsObject(user), phone.kind == value.KindNull, value.KindObject
mut copy := value.ValueClone(user)
println value.Equal(copy, user)
removed := value.ObjectDelete(copy, "phone")
println removed, value.Equal(copy, user)
mut numbers := Vector {} as Vector<value.Value>
numbers.push(value.ValueInt(1))
mut fields := HashMap {} as HashMap<str, value.Value>
fields.put("x", value.ValueArray(numbers))
println value.ToDebugString(value.ValueObject(fields))
value.ValueFree(copy)
endConstants and variables
KindNull
const KindNull: i32 = 0The example for this item is shown with the ones above: encoding-value-Value.salam
KindBool
const KindBool: i32 = 1The example for this item is shown with the ones above: encoding-value-Value.salam
KindInt
const KindInt: i32 = 2The example for this item is shown with the ones above: encoding-value-Value.salam
KindFloat
const KindFloat: i32 = 3The example for this item is shown with the ones above: encoding-value-Value.salam
KindString
const KindString: i32 = 4The example for this item is shown with the ones above: encoding-value-Value.salam
KindArray
const KindArray: i32 = 5The example for this item is shown with the ones above: encoding-value-Value.salam
KindObject
const KindObject: i32 = 6The example for this item is shown with the ones above: encoding-value-Value.salam
Functions
ToDebugString
func ToDebugString(v: Value): strobject{name: string(Ali)}, for debugging.The example for this item is shown with the ones above: encoding-value-Value.salam
ValueNull
func ValueNull(): ValueThe example for this item is shown with the ones above: encoding-value-Value.salam
ValueBool
func ValueBool(b: bool): ValueThe example for this item is shown with the ones above: encoding-value-Value.salam
ValueInt
func ValueInt(i: i64): ValueThe example for this item is shown with the ones above: encoding-value-Value.salam
ValueFloat
func ValueFloat(n: f64): ValueThe example for this item is shown with the ones above: encoding-value-Value.salam
ValueString
func ValueString(s: str): ValueThe example for this item is shown with the ones above: encoding-value-Value.salam
ValueArray
func ValueArray(items: Vector<Value>): ValueThe example for this item is shown with the ones above: encoding-value-Value.salam
ValueObject
func ValueObject(fields: HashMap<str, Value>): ValueThe example for this item is shown with the ones above: encoding-value-Value.salam
NewValueArray
func NewValueArray(): ValueThe example for this item is shown with the ones above: encoding-value-Value.salam
NewValueObject
func NewValueObject(): ValueThe example for this item is shown with the ones above: encoding-value-Value.salam
IsNull
func IsNull(v: Value): boolThe example for this item is shown with the ones above: encoding-value-Value.salam
IsBool
func IsBool(v: Value): boolThe example for this item is shown with the ones above: encoding-value-Value.salam
IsInt
func IsInt(v: Value): boolThe example for this item is shown with the ones above: encoding-value-Value.salam
IsFloat
func IsFloat(v: Value): boolThe example for this item is shown with the ones above: encoding-value-Value.salam
IsNumber
func IsNumber(v: Value): boolThe example for this item is shown with the ones above: encoding-value-Value.salam
IsString
func IsString(v: Value): boolThe example for this item is shown with the ones above: encoding-value-Value.salam
IsArray
func IsArray(v: Value): boolThe example for this item is shown with the ones above: encoding-value-Value.salam
IsObject
func IsObject(v: Value): boolThe example for this item is shown with the ones above: encoding-value-Value.salam
AsBool
func AsBool(v: Value): boolThe example for this item is shown with the ones above: encoding-value-Value.salam
AsInt
func AsInt(v: Value): i64The example for this item is shown with the ones above: encoding-value-Value.salam
AsFloat
func AsFloat(v: Value): f64The example for this item is shown with the ones above: encoding-value-Value.salam
AsString
func AsString(v: Value): strThe example for this item is shown with the ones above: encoding-value-Value.salam
ArrayLen
func ArrayLen(v: Value): intThe example for this item is shown with the ones above: encoding-value-Value.salam
ArrayGet
func ArrayGet(v: Value, i: int): Valuei; an index outside 0 to ArrayLen - 1 stops the program.The example for this item is shown with the ones above: encoding-value-Value.salam
ArraySet
func ArraySet(v &: Value, i: int, item: Value)i.The example for this item is shown with the ones above: encoding-value-Value.salam
ArrayPush
func ArrayPush(v &: Value, item: Value)ObjectGet or ArrayGet, store it back for the parent to see the new length.The example for this item is shown with the ones above: encoding-value-Value.salam
ObjectLen
func ObjectLen(v: Value): intThe example for this item is shown with the ones above: encoding-value-Value.salam
ObjectHas
func ObjectHas(v: Value, key: str): boolThe example for this item is shown with the ones above: encoding-value-Value.salam
ObjectGet
func ObjectGet(v: Value, key: str): Valuev is not an object.The example for this item is shown with the ones above: encoding-value-Value.salam
ObjectGetOk
func ObjectGetOk(v: Value, key: str, ok &: bool): Valueok saying whether the key was there; for telling "absent" apart from "null".The example for this item is shown with the ones above: encoding-value-Value.salam
ObjectSet
func ObjectSet(v &: Value, key: str, item: Value)The example for this item is shown with the ones above: encoding-value-Value.salam
ObjectDelete
func ObjectDelete(v &: Value, key: str): boolThe example for this item is shown with the ones above: encoding-value-Value.salam
ObjectKeys
func ObjectKeys(v: Value): Vector<str>ObjectSet come in the order they were added; the keys of an object made from a map by ValueObject come in that map's own order.The example for this item is shown with the ones above: encoding-value-Value.salam
ValueClone
func ValueClone(v: Value): ValueThe example for this item is shown with the ones above: encoding-value-Value.salam
ValueFree
func ValueFree(v &: Value)The example for this item is shown with the ones above: encoding-value-Value.salam
Equal
func Equal(a: Value, b: Value): boolThe example for this item is shown with the ones above: encoding-value-Value.salam