nupp.serde.json

This codec writes and reads JSON text through any nupp.serde.Binding. See Serialization bindings for field mappings.

local serde = require("nupp.serde")
local json = require("nupp.serde.json")
local record Order
    id: integer
    items: {string}
    note: string?
end
local orders = serde.binding(Order)
local bytes = json.encode(orders, new Order(id = 9, items = {"tea"}))
assert(bytes == '{"id":9,"items":["tea"],"note":null}')
local order = json.decode(orders, bytes)
assert(order.items[1] == "tea" and order.note == nil)

What a binding needs to be written is worked out on first use and kept, so the second call with a binding does none of that work again. Native AOT builds also select a compiled decoder for bindings whose carriers it supports.

The module-level functions share one Codec with defaultPolicy, which reads declaration bindings and documents. A model library passes a PolicyFactory of its own to codec, or memberNamePolicy when its member names are already the wire names.

Decoding is strict: a declaration binding rejects unknown members unless its nupp.serde.Options say unknownMembers = "ignore", integers must fit their declared width, and nesting and token counts are bounded. Every failure is a nupp.serde.Error naming the path and byte offset where it happened.

Module contents

Types

TypeKindDescription
CodecinterfaceA JSON codec with its own policy and its own cache of what it selected for each binding.
FieldNamerecordThe wire field a JSON decoder reports for a member the model does not declare.
PolicyinterfaceChooses each member's JSON name, whether it is written, and how its timestamps are represented.
PolicyFactoryinterfaceReturns the Policy for a binding's member type.
SelectiontypeWhether a policy writes a member, leaves it out, or writes it redacted.

Functions

FunctionKindDescription
codecfunctionMakes a Codec.
decodefunctionDecodes JSON text into a value.
decodeBufferfunctionDecodes a caller-owned Buffer, reading it in place when the provider can.
decodeDocumentfunctionDecodes JSON text as document contents, attaching the context it was read under.
documentPolicyfunctionReturns the policy JSON applies inside a document-valued member, or nil when the member is not one of a document's own.
encodefunctionEncodes a value as JSON text.
encodeDocumentfunctionEncodes a document's contents as JSON text.
encodeIntofunctionAppends a value's JSON text to a caller-owned Buffer.
fieldNamefunctionReturns the JSON member name of a wire field.
writeValuefunctionWrites one value at the current position of a nupp.codec.json.Writer, for embedding bound values in JSON assembled...

Values

ValueKindDescription
defaultPolicyvariableThe policy for declaration bindings and documents, which the module-level functions use.
identityvariableIdentifies JSON as the codec in a nupp.serde.Context it attaches, so a protocol can tell a document it read from one...
memberNamePolicyvariableThe policy that writes every member under its own name, traverses every member, and writes timestamps as RFC 3339...

Types#

Codecinterface#

interface Codec ...

A JSON codec with its own policy and its own cache of what it selected for each binding.

The module-level functions use one shared codec with defaultPolicy. Hold a codec of your own to use another policy, or to bound and clear what it retains.

Members

NameKindDescription
encodeDocumentmethodEncodes a document's contents.
decodeDocumentmethodDecodes document contents.
writeValuemethodWrites one value into a JSON writer.
encodemethodEncodes a value.
encodeIntomethodAppends a value to a buffer.
decodemethodDecodes a value.
decodeBuffermethodDecodes a value from a buffer.
clearmethodForgets every binding's selections.
cachedBindingsmethodReturns how many bindings have selections retained.

encodeDocumentmethod#

encodeDocument: function<D>(self, adapter: core.DocumentAdapter<D>, borrows value: D): string

Encodes a document's contents. See encodeDocument.

Arguments
NameTypeDescription
?self
adaptercore.DocumentAdapter<D>
borrows valueD
Returns
TypeDescription
string

decodeDocumentmethod#

decodeDocument: function<D>(self, adapter: core.DocumentAdapter<D>, bytes: string, context: core.Context?): D

Decodes document contents. See decodeDocument.

Arguments
NameTypeDescription
?self
adaptercore.DocumentAdapter<D>
bytesstring
contextcore.Context?
Returns
TypeDescription
D

writeValuemethod#

writeValue: function<T>(self, binding: core.Binding<T>, borrows value: T, exclusive sink: json.Writer): nil

Writes one value into a JSON writer. See writeValue.

Arguments
NameTypeDescription
?self
bindingcore.Binding<T>
borrows valueT
exclusive sinkjson.Writer
Returns
TypeDescription
nil

encodemethod#

encode: function<T>(self, binding: core.Binding<T>, borrows value: T): string

Encodes a value. See encode.

Arguments
NameTypeDescription
?self
bindingcore.Binding<T>
borrows valueT
Returns
TypeDescription
string

encodeIntomethod#

encodeInto: function<T>(self, binding: core.Binding<T>, borrows value: T, exclusive buffer: text.Buffer): nil

Appends a value to a buffer. See encodeInto.

Arguments
NameTypeDescription
?self
bindingcore.Binding<T>
borrows valueT
exclusive buffertext.Buffer
Returns
TypeDescription
nil

decodemethod#

decode: function<T>(self, binding: core.Binding<T>, bytes: string, context: core.Context?): T

Decodes a value. See decode.

Arguments
NameTypeDescription
?self
bindingcore.Binding<T>
bytesstring
contextcore.Context?
Returns
TypeDescription
T

decodeBuffermethod#

decodeBuffer: function<T>(self, binding: core.Binding<T>, exclusive input: text.Buffer, context: core.Context?): T

Decodes a value from a buffer. See decodeBuffer.

Arguments
NameTypeDescription
?self
bindingcore.Binding<T>
exclusive inputtext.Buffer
contextcore.Context?
Returns
TypeDescription
T

clearmethod#

clear: function(self): nil

Forgets every binding's selections.

Arguments
NameTypeDescription
?self
Returns
TypeDescription
nil

cachedBindingsmethod#

cachedBindings: function(self): integer

Returns how many bindings have selections retained.

Arguments
NameTypeDescription
?self
Returns
TypeDescription
integer

FieldNamerecord#

record FieldName is core.WireField ...

The wire field a JSON decoder reports for a member the model does not declare.

Members

NameKindDescription
namefieldThe member name from the JSON object, with its escapes decoded.
diagnosticLabelmethodReturns the name, for messages such as an unknown member's.

namefield#

name: string
@readonly

The member name from the JSON object, with its escapes decoded.

diagnosticLabelmethod#

diagnosticLabel: function diagnosticLabel(self): string

Returns the name, for messages such as an unknown member's.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
string

the member name

Policyinterface#

interface Policy<M is core.Member> ...

Chooses each member's JSON name, whether it is written, and how its timestamps are represented.

A model library implements one to interpret the traits on its own members, such as a Smithy jsonName or timestampFormat.

Type parameters

NameDescription
M

Members

NameKindDescription
namemethodReturns the JSON member name of a structure or union member.
selectmethodReturns whether a member is traversed, omitted, or redacted.
timestampmethodEncodes a timestamp member's value as JSON.
parseTimestampmethodDecodes a timestamp member's JSON value.

namemethod#

name: function(self, member: M): string

Returns the JSON member name of a structure or union member.

The codec asks while planning a binding, and as it writes each member of a dynamic value such as a document. Two members of a structure that this policy gives the same name fail the plan unless one of them is omitted.

Arguments
NameTypeDescription
?self
memberM

the member to name

Returns
TypeDescription
string

the JSON name

selectmethod#

select: function(self, member: M, direction: core.Direction): Selection

Returns whether a member is traversed, omitted, or redacted.

The codec asks about every member a binding reaches when it plans it for a direction. Reading fails the plan when a member that is not traversed is required and has no default.

Arguments
NameTypeDescription
?self
memberM

the member to select

directioncore.Direction

whether the plan is for writing or reading

Returns
TypeDescription
Selection

the selection

timestampmethod#

timestamp: function(self, member: M, borrows value: core.Timestamp): json.EncodedValue

Encodes a timestamp member's value as JSON.

Arguments
NameTypeDescription
?self
memberM

the member being written

borrows valuecore.Timestamp

the timestamp

Returns
TypeDescription
json.EncodedValue

the encoded JSON value, such as an RFC 3339 string or a number of epoch seconds

parseTimestampmethod#

parseTimestamp: function(self, member: M, text: string, kind: "string" | "number"): core.Timestamp

Decodes a timestamp member's JSON value.

The codec rejects a value that is neither a string nor a number before asking.

Arguments
NameTypeDescription
?self
memberM

the member being read

textstring

the string's contents, or the number's lexeme

kind"string" | "number"

which of the two the input held

Returns
TypeDescription
core.Timestamp

the timestamp

PolicyFactoryinterface#

interface PolicyFactory ...

Returns the Policy for a binding's member type.

Members

NameKindDescription
policymethodReturns the policy for a binding whose value sits at a member.

policymethod#

policy: function<M is core.Member>(self, member: M): Policy<M>

Returns the policy for a binding whose value sits at a member.

A codec asks when it first plans a binding for a direction, and keeps the policy with that plan while the binding stays in its cache. The one policy serves every member the binding reaches.

Arguments
NameTypeDescription
?self
memberM

the member the binding's root value sits at

Returns
TypeDescription
Policy<M>

the policy

Selectiontype#

type Selection = "traverse" | "omit" | "redact"

Whether a policy writes a member, leaves it out, or writes it redacted.

Functions#

codecfunction#

function codec(policy: plans.PolicyFactory?, capacity: integer?): Codec

Makes a Codec.

local serde = require("nupp.serde")
local json = require("nupp.serde.json")
local record Item
    id: integer
end
local codec = json.codec(json.defaultPolicy, 16)
local items = serde.binding(Item)
assert(codec:decode(items, codec:encode(items, new Item(id = 3))).id == 3)
assert(codec:cachedBindings() == 1)
codec:clear()

Arguments

NameTypeDescription
policyplans.PolicyFactory?

the policy; nil is defaultPolicy

capacityinteger?

the most bindings to retain selections for; nil is 256

Returns

TypeDescription
Codec

the codec

decodefunction#

function decode<T>(binding: core.Binding<T>, bytes: string, context: core.Context?): T

Decodes JSON text into a value.

The whole input must be one value; trailing text is an error. A decoded value the codec then has to reject is dropped through the binding.

local serde = require("nupp.serde")
local json = require("nupp.serde.json")
local record Range
    low: integer
    high: integer = 100
end
local range = json.decode(serde.binding(Range), '{"low":5}')
assert(range.low == 5 and range.high == 100)

Type parameters

NameDescription
T

Arguments

NameTypeDescription
bindingcore.Binding<T>

the binding to construct through

bytesstring

the JSON text

contextcore.Context?

a parent for the context the decoder attaches, or nil

Returns

TypeDescription
T

the value

Raises

TypeCondition
nupp.serde.Error

with the path and byte offset of the failure

decodeBufferfunction#

function decodeBuffer<T>(binding: core.Binding<T>, exclusive input: text.Buffer, context: core.Context?): T

Decodes a caller-owned Buffer, reading it in place when the provider can.

Type parameters

NameDescription
T

Arguments

NameTypeDescription
bindingcore.Binding<T>

the binding to construct through

exclusive inputtext.Buffer

the buffer holding the JSON text

contextcore.Context?

a parent for the context the decoder attaches, or nil

Returns

TypeDescription
T

the value

Raises

TypeCondition
nupp.serde.Error

with the path and byte offset of the failure

decodeDocumentfunction#

function decodeDocument<D>(adapter: core.DocumentAdapter<D>, bytes: string, context: core.Context?): D

Decodes JSON text as document contents, attaching the context it was read under.

Type parameters

NameDescription
D

Arguments

NameTypeDescription
adaptercore.DocumentAdapter<D>

the document adapter, such as nupp.serde.document.adapter

bytesstring

the JSON text

contextcore.Context?

a parent for the attached context, or nil

Returns

TypeDescription
D

the document

Raises

TypeCondition
nupp.serde.Error

with the path and byte offset of the failure

documentPolicyfunction#

function documentPolicy<M is core.Member>(member: M, epochTimestamps: boolean): plans.Policy<M>?

Returns the policy JSON applies inside a document-valued member, or nil when the member is not one of a document's own.

A model's PolicyFactory asks this first, so documents nested in its values are read and written the way defaultPolicy reads them.

Type parameters

NameDescription
M

Arguments

NameTypeDescription
memberM

the member a codec asks about

epochTimestampsboolean

whether document timestamps are written as epoch seconds

Returns

TypeDescription
plans.Policy<M>?

the policy, or nil

encodefunction#

function encode<T>(binding: core.Binding<T>, borrows value: T): string

Encodes a value as JSON text.

local serde = require("nupp.serde")
local json = require("nupp.serde.json")
local record Pair
    left: string
    right: string?
end
assert(json.encode(serde.binding(Pair), new Pair(left = "a")) == '{"left":"a","right":null}')

Type parameters

NameDescription
T

Arguments

NameTypeDescription
bindingcore.Binding<T>

the value's binding

borrows valueT

the value

Returns

TypeDescription
string

the JSON text

Raises

TypeCondition
nupp.serde.Error

when a value cannot be represented, at its path

encodeDocumentfunction#

function encodeDocument<D>(adapter: core.DocumentAdapter<D>, borrows value: D): string

Encodes a document's contents as JSON text.

Type parameters

NameDescription
D

Arguments

NameTypeDescription
adaptercore.DocumentAdapter<D>

the document adapter, such as nupp.serde.document.adapter

borrows valueD

the document

Returns

TypeDescription
string

the JSON text

Raises

TypeCondition
nupp.serde.Error

when a value has no JSON representation

encodeIntofunction#

function encodeInto<T>(binding: core.Binding<T>, borrows value: T, exclusive buffer: text.Buffer): nil

Appends a value's JSON text to a caller-owned Buffer.

On failure the buffer is restored to what it held before the call.

Type parameters

NameDescription
T

Arguments

NameTypeDescription
bindingcore.Binding<T>

the value's binding

borrows valueT

the value

exclusive buffertext.Buffer

the buffer to append to

Returns

TypeDescription
nil

Raises

TypeCondition
nupp.serde.Error

when a value cannot be represented, at its path

fieldNamefunction#

function fieldName(field: core.WireField): string

Returns the JSON member name of a wire field.

Arguments

NameTypeDescription
fieldcore.WireField

Returns

TypeDescription
string

writeValuefunction#

function writeValue<T>(binding: core.Binding<T>, borrows value: T, exclusive sink: json.Writer): nil

Writes one value at the current position of a nupp.codec.json.Writer, for embedding bound values in JSON assembled by hand.

local serde = require("nupp.serde")
local json = require("nupp.serde.json")
local record Tag
    name: string
end
local buffer = nupp.text.newBuffer()
local writer = nupp.codec.json.newWriter(buffer)
writer:startArray()
json.writeValue(serde.binding(Tag), new Tag(name = "x"), writer)
writer:endArray()
writer:close()
assert(buffer:tostring() == '[{"name":"x"}]')

Type parameters

NameDescription
T

Arguments

NameTypeDescription
bindingcore.Binding<T>

the value's binding

borrows valueT

the value

exclusive sinkjson.Writer

the writer, positioned where a value may go

Returns

TypeDescription
nil

Raises

TypeCondition
nupp.serde.Error

when a value cannot be represented, at its path

Values#

defaultPolicyvariable#

The policy for declaration bindings and documents, which the module-level functions use. A model's own factory can delegate to it for members it does not own.

identityvariable#

Identifies JSON as the codec in a nupp.serde.Context it attaches, so a protocol can tell a document it read from one another codec read.

memberNamePolicyvariable#

The policy that writes every member under its own name, traverses every member, and writes timestamps as RFC 3339 strings.

defaultPolicy refuses a model's own members rather than guess at the traits they carry, so a model library has to say how its members are named. Use it for a model whose names are already the wire names.

local serde = require("nupp.serde")
local adapters = require("nupp.serde.adapters")
local json = require("nupp.serde.json")
local record Member is serde.Member
    @readonly name: string
    @readonly index: integer?
end
local title: serde.Binding<string> = new serde.Bound<string, Member>(
    adapter = adapters.text(new Member(name = "title"))
)
assert(not pcall(json.encode, title, "Dune"))
local plain = json.codec(json.memberNamePolicy)
assert(plain:encode(title, "Dune") == '"Dune"')