Skip to content
jess
api

@jess/data/value

Build a value to write as JSON, YAML, or TOML: ordered maps, arrays, variants, forced floats, and the one null.

require('@jess/data/value'): { array, map, set, struct, variant, float, kindOf, encode, shortest, display, Null, None }

@jess/data/value is the value model behind encode on @jess/json, @jess/yaml, and @jess/data/toml, Luau in the built-in package. It needs no capability.

A Luau table says neither whether it is a list or a map, nor what order its keys come in, nor whether a missing field is null or absent. A writer that guessed would write a different document from the same data on another day, and a document a tool writes gets diffed, hashed, and read back by other tools. So a value is built, not inferred: lists come from array, maps from map and set or from struct, and a bare table is refused by name.

The text the writers produce is byte for byte what a Rust program's serializer writes for the same value: serde_json's compact JSON, serde_yaml's block style, and the toml crate's pretty documents. That is what jess's own commands print, so a document a script writes and a document jess package inspect writes look the same.

The model§

KindBuilt byJSONYAMLTOML
nulljess.null, also value.Nullnullnullan error: TOML has no null
nonevalue.None, or nil in a struct fieldnullnullleft out of a table, an error inside an array
boolean, stringthe Luau valueas themselvesas themselves, quoted when a reader would take them for something elseas themselves
inta whole number below 2^53, or an integer333
floata fractional number, or value.float(n)1.01.01.0
arrayvalue.array { ... }[...]one item per line[...]
mapvalue.map() and value.set, or value.struct{...}, keys in orderkeys in ordervalues first, then tables
variantvalue.variant(tag, value){"tag": value}!tag value[tag] table

value.array, value.map, value.set, value.struct§

value.array(items: { Value }) -> { Value }; value.map() -> Map; value.set(target: Map, key: string, value: Value) -> Map; value.struct(fields: { { unknown } }) -> Map

array marks a table as a list and returns it. map makes an empty ordered map, { keys, values }, and set adds a key to it, keeping the first position a key was given, so setting a key again changes its value and not its place. struct makes a map from { key, value } pairs in order, the way a struct serializes its fields; a pair whose value is nil becomes None, an absent field.

--!strict
local value = require '@jess/data/value'

local options = value.map()
value.set(options, 'verbose', true)
value.set(options, 'name', 'greeter')
value.set(options, 'verbose', false)
print(table.concat(options.keys, ', '), options.values.verbose)
print(value.encode(options, 'json'))
verbose, name	false
{"verbose":false,"name":"greeter"}

value.variant, value.float§

value.variant(tag: string, value: Value) -> Value; value.float(value: number) -> Value

variant is an enum variant with a payload: a tagged value, written as one-key object, !tag, or a table by format. float is a number that is always written as a float, so 1 comes out as 1.0; without it a whole number is an integer.

value.kindOf§

value.kindOf(value: Value) -> string

Which of the kinds above a value is: 'null', 'none', 'boolean', 'string', 'int', 'float', 'array', 'map', or 'variant'.

  • Errors: data: a bare table is neither a list nor a map: build it with array, map, or struct for a table none of the builders made; data: a <type> can't be written for a function, buffer, or anything else no format holds.
--!strict
local value = require '@jess/data/value'

print(value.kindOf(1), value.kindOf(1.5), value.kindOf(value.float(1)), value.kindOf(jess.null), value.kindOf(value.None))
print(value.kindOf(value.array {}), value.kindOf(value.map()), value.kindOf(value.variant('a', 1)))
int	float	float	null	none
array	map	variant

value.encode§

value.encode(value: Value, format: 'json' | 'yaml' | 'toml') -> string

Writes a value in one of the three formats. The codecs' own encode functions call this with their format; it's here for code that picks the format at run time.

  • Errors: data: unknown format '<name>' (expected json, yaml, or toml), plus whatever the format refuses: a null in TOML (toml: unsupported unit type), a None inside a TOML array (toml: unsupported None value), a bare table anywhere.
--!strict
local json = require '@jess/json'
local toml = require '@jess/data/toml'
local value = require '@jess/data/value'
local yaml = require '@jess/yaml'

local record = value.struct {
  { 'id', 'com.example.greeter' },
  { 'ratio', value.float(1) },
  { 'note', nil },
  { 'layers', value.array { 'example.greetings' } },
  { 'kind', value.variant('command', 'greet') },
}

print(json.encode(record))
print(yaml.encode(record))
print(toml.encode(record))
{"id":"com.example.greeter","ratio":1.0,"note":null,"layers":["example.greetings"],"kind":{"command":"greet"}}
id: com.example.greeter
ratio: 1.0
note: null
layers:
- example.greetings
kind: !command greet

id = "com.example.greeter"
ratio = 1.0
layers = ["example.greetings"]

[kind]
command = "greet"

The same record, three documents: note is null twice and absent once, and the variant is an object, a tag, and a table.

value.shortest, value.display§

value.shortest(value: number, plus: boolean) -> string; value.display(value: number) -> string

The two float spellings the writers use, for code that wants to match them. shortest is the shortest round-tripping decimal in Ryū's layout, fixed notation for decimal exponents from -5 to 15 and scientific past them, with plus writing the + on a positive exponent as serde_json does. display is Rust's Display for a float: never an exponent.

--!strict
local value = require '@jess/data/value'

print(value.shortest(1, false), value.shortest(1e20, true), value.display(1e20), value.display(0.1))
1.0	1e+20	100000000000000000000	0.1

Related: @jess/json, @jess/yaml, @jess/data/toml.