nupp.codec.json
nupp.codec.json is a strict Nupp JSON codec. Its performance provider is SIMD-accelerated through @aot; portable compiler and browser targets select a pure-Lua provider behind the same API. It parses, pulls selected fields, serializes, and streams.
local encoded = nupp.codec.json.encode({name = "Nupp", ready = true})
local decoded = nupp.codec.json.decode(encoded)
assert(decoded.name == "Nupp")The codec accepts one complete UTF-8 JSON document and rejects invalid numbers, sparse arrays, mixed-key containers, cycles, and excessive nesting. serialize is an alias of encode.
Empty containers#
A plain empty Lua table has no shape to read, so it encodes as {}. Mark it when it must encode as an array, or use the two sentinels:
const json = nupp.codec.json
assert(json.encode({}) == "{}")
assert(json.encode(json.asArray({})) == "[]")
assert(json.encode(json.EMPTY_ARRAY) == "[]")
assert(json.encode(json.EMPTY_OBJECT) == "{}")
local emptyArray = json.decode("[]")
assert(json.isArray(emptyArray))
assert(not json.isArray(json.decode("{}")))asObject is the corresponding mark for a table that must encode as an object. isArray reads the shape retained by decoded containers, including when they are empty.
JSON null#
Decoding drops JSON null by default, including from inside an array. Pass a replacement value to preserve it. NULL is the round-trippable choice, because it encodes back as null:
const json = nupp.codec.json
local value = json.decode([[{"items":[1,null,2]}]], json.NULL)
assert(value.items[2] == json.NULL)
assert(json.encode(value) == [[{"items":[1,null,2]}]])Dropping null by default
A Lua table cannot hold nil as a value, so a decoded null has to become either an absence or a sentinel. An absence is what most callers mean, and it keeps a decoded document indexable without every read testing against a sentinel first. A sentinel is what a caller round-tripping somebody else's document means, and that caller says so once, at the decode call, rather than everywhere the value is read.
Pulling selected values#
pull validates the complete document while applying its selection. true selects a complete value, false drops it, an object shape selects named fields, and arrayOf(shape) applies a shape to every member of an array. Missing and unselected fields are omitted:
const json = nupp.codec.json
local users = json.pull(source, json.arrayOf({id = true, profile = {name = true}}))This is the lower-level path for pull deserializers: it validates the complete input but constructs only the Lua values the shape asks for.
Streaming output#
The nupp.Closeable writer emits checked JSON into caller-owned storage without first building a Lua table or a complete result string. endArray() and endObject() end containers, flush() publishes a bounded staged batch while leaving the document open, and consuming close() requires one complete root:
local out = nupp.text.buffer.new()
local writer = nupp.codec.json.writer(out)
writer:startObject():key("items"):startArray():write(1):write(2):endArray():endObject()
writer:close()Lexical destruction closes a live writer automatically. The public writer identity is single-use. Closing publishes its final staged parts and leaves that identity closed; untyped stale references therefore cannot affect a later document.
A prepared nupp.serde JSON traversal can write at the current value position. Its recursive walk drains completed fields and list members at the same threshold, so a large root does not need to exist as one staged string.
encoded(value) performs ordinary encoding once. verified(text) instead validates an existing immutable JSON string without decoding or re-encoding it. Both return a value that write appends without another walk or validation. encodedString(value) and verifiedString(text) provide the corresponding string-only form accepted by both key and write; these handles are interned, so repeated schema keys share their encoded representation.
Derived records#
For a record deriving nupp.derive.JSON, writeRecord(value, writer) discovers the record's type witness from the value. writeAs(Record, value, writer) and decodeAs(Record, text) accept the visible record name directly. encodeRecord and encodeAs allocate and return a complete string for callers that specifically need one.
The generated writeJSON(writer) member writes through the same checked API, so a derived record can occupy the root or any value position in a larger document. Derived schemas lazily cache their encoded field names and literal values.
These five members are installed onto the module by the derive runtime rather than answered by the codec, so each raises when nothing derived JSON for the type it was handed.
Module contents
Constructors
| Constructor | Description |
|---|---|
newCodec | Creates a schema-driven JSON codec with one immutable wire profile. |
Types
| Type | Kind | Description |
|---|---|---|
EncodedString | record | A JSON string whose immutable encoded bytes have already been produced or validated. |
EncodedValue | record | A complete JSON value whose immutable bytes have already been encoded or validated. |
JSONEncodable | interface | Values that can write one complete JSON value through a checked writer. |
Writer | interface | A checked incremental JSON writer. |
Functions
| Function | Kind | Description |
|---|---|---|
arrayOf | function | Applies one pull shape to every member of an array. |
asArray | function | Marks a Lua table as a JSON array, including while it is empty. |
asObject | function | Marks a Lua table as a JSON object. |
decode | function | Decodes one complete JSON document. |
decodeAs | function | Decodes JSON into the nominal record named by a type witness. |
encode | function | Serializes one Lua value as JSON. |
encodeAs | function | Encodes a nominal record named by a type witness into a new string. |
encoded | function | Encodes a Lua value once for trusted raw insertion by a Writer. |
encodedString | function | Encodes and interns one string for trusted use as either a key or value. |
encodeRecord | function | Encodes a nominal record using its own metatable into a new string. |
isArray | function | Answers whether a value has JSON array shape. |
pull | function | Materializes only the fields selected by an On-Demand pull shape. |
serialize | function | Serializes one Lua value as JSON; an alias of encode. |
verified | function | Validates existing JSON bytes once without decoding or re-encoding them. |
verifiedString | function | Validates and interns existing bytes containing one complete JSON string. |
writeAs | function | Writes a nominal record after explicitly supplying its type witness. |
writer | function | Creates an incremental JSON writer over caller-owned storage. |
writeRecord | function | Writes a nominal record using its own metatable as the type witness. |
Values
| Value | Kind | Description |
|---|---|---|
EMPTY_ARRAY | variable | An explicit empty JSON array. |
EMPTY_OBJECT | variable | An explicit empty JSON object. |
NULL | variable | Sentinel that decodes from and encodes as JSON null. |
Constructors#
newCodecconstructor#
Creates a schema-driven JSON codec with one immutable wire profile.
The codec prepares a nupp.serde.Binding<T> once and then performs each complete scalar structure traversal in one native call. Format-owning JSON derives remain available as compatibility APIs.
(nupp.derive.Serde)
local record User
id: uint32
end
local codec = nupp.codec.json.newCodec()
local prepared = codec:prepare(nupp.serde.of(User))
assert(prepared:encode(new User(id = 41)) == [[{"id":41}]])Arguments
| Name | Type | Description |
|---|---|---|
options | any? | fieldNames and unknownMembers profile policy |
Returns
| Type | Description |
|---|---|
any | the schema-driven codec |
Raises
when the profile is invalid
Types#
EncodedStringrecord#
record EncodedString
endA JSON string whose immutable encoded bytes have already been produced or validated.
Writer:key accepts this type without validating UTF-8 or escaping it again. Values produced by encodedString and verifiedString are interned.
EncodedValuerecord#
record EncodedValue
endA complete JSON value whose immutable bytes have already been encoded or validated.
Values come from encoded or verified; the private token prevents checked callers from manufacturing an unverified fast-path value.
JSONEncodableinterface#
Values that can write one complete JSON value through a checked writer.
Declared here rather than aliased out of the host boundary, because this is the name callers write: derive.JSON answers a contract in terms of it. Writer below names the incremental encoder returned by writer().
local record Money is nupp.codec.json.JSONEncodable
cents: integer
function writeJSON(self, exclusive out: nupp.codec.json.Writer): nil
out:startObject():key("cents"):write(self.cents):endObject()
end
endMethods
Writerinterface#
affine interface Writer is nupp.Closeable
startArray: function(exclusive self: Writer): Writer borrows (self)
startObject: function(exclusive self: Writer): Writer borrows (self)
key: function(exclusive self: Writer, name: string): Writer borrows (self)
& function(exclusive self: Writer, name: EncodedString): Writer borrows (self)
write: function(
exclusive self: Writer,
value: string | number | boolean | {any} | {[string]: any}
): Writer borrows (self)
& function(exclusive self: Writer, value: EncodedValue | EncodedString): Writer borrows (self)
null: function(exclusive self: Writer): Writer borrows (self)
endArray: function(exclusive self: Writer): Writer borrows (self)
endObject: function(exclusive self: Writer): Writer borrows (self)
flush: function(exclusive self: Writer): nil
terminal close: function(takes self: Writer): nil
endA checked incremental JSON writer.
local out = nupp.text.buffer.new()
local writer = nupp.codec.json.writer(out)
writer:startObject():key("ok"):write(true):endObject()
writer:close()
assert(out:tostring() == [[{"ok":true}]])Methods
startArray#
Starts an array value.
local out = nupp.text.buffer.new()
local writer = nupp.codec.json.writer(out)
writer:startArray():write("first"):endArray()
writer:close()
assert(out:tostring() == "[\"first\"]")Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | Writer | the writer |
Returns
| Type | Description |
|---|---|
Writer borrows (self) | this writer |
startObject#
Starts an object value.
local out = nupp.text.buffer.new()
local writer = nupp.codec.json.writer(out)
writer:startObject():endObject()
writer:close()
assert(out:tostring() == "{}")Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | Writer | the writer |
Returns
| Type | Description |
|---|---|
Writer borrows (self) | this writer |
key#
key: function(exclusive self: Writer, name: string): Writer borrows (self)
& function(exclusive self: Writer, name: EncodedString): Writer borrows (self)Selects the next object member.
local out = nupp.text.buffer.new()
local writer = nupp.codec.json.writer(out)
writer:startObject():key("answer"):write(42):endObject()
writer:close()
assert(out:tostring() == [[{"answer":42}]])Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | Writer | the writer |
name | string | the next member name |
Returns
| Type | Description |
|---|---|
Writer borrows (self) | this writer |
write#
write: function(
exclusive self: Writer,
value: string | number | boolean | {any} | {[string]: any}
): Writer borrows (self)
& function(exclusive self: Writer, value: EncodedValue | EncodedString): Writer borrows (self)Appends one complete Lua value.
local out = nupp.text.buffer.new()
local writer = nupp.codec.json.writer(out)
writer:startArray():write({id = 41}):endArray()
writer:close()
assert(out:tostring() == "[{\"id\":41}]")Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | Writer | the writer |
value | string | number | boolean | {any} | {[string]: any} | the next JSON value |
Returns
| Type | Description |
|---|---|
Writer borrows (self) | this writer |
null#
Appends JSON null.
local out = nupp.text.buffer.new()
local writer = nupp.codec.json.writer(out)
writer:startArray():null():endArray()
writer:close()
assert(out:tostring() == "[null]")Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | Writer | the writer |
Returns
| Type | Description |
|---|---|
Writer borrows (self) | this writer |
endArray#
Ends the current array.
local out = nupp.text.buffer.new()
local writer = nupp.codec.json.writer(out)
writer:startArray():startObject():endObject():endArray()
writer:close()
assert(out:tostring() == "[{}]")Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | Writer | the writer |
Returns
| Type | Description |
|---|---|
Writer borrows (self) | this writer |
endObject#
Ends the current object.
local out = nupp.text.buffer.new()
local writer = nupp.codec.json.writer(out)
writer:startObject():key("items"):startArray():endArray():endObject()
writer:close()
assert(out:tostring() == [[{"items":[]}]])Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | Writer | the writer |
Returns
| Type | Description |
|---|---|
Writer borrows (self) | this writer |
flush#
flush: function(exclusive self: Writer): nilPublishes staged bytes without ending the document.
local out = nupp.text.buffer.new()
local writer = nupp.codec.json.writer(out)
writer:startArray():write(1)
writer:flush()
assert(out:tostring() == "[1")
writer:endArray():close()Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | Writer | the writer |
Returns
| Type | Description |
|---|---|
nil |
close#
Verifies and publishes one complete root, then releases backing state.
local out = nupp.text.buffer.new()
local writer = nupp.codec.json.writer(out)
writer:write({ok = true})
writer:close()
assert(out:tostring() == [[{"ok":true}]])Arguments
| Name | Type | Description |
|---|---|---|
takes self | Writer | the writer |
Returns
| Type | Description |
|---|---|
nil |
Functions#
arrayOffunction#
Applies one pull shape to every member of an array.
const json = nupp.codec.json
local users = json.pull(
[[{"users":[{"id":1,"name":"Ada"},{"id":2,"name":"Lin"}]}]],
{users = json.arrayOf({id = true})}
)
assert(users.users[1].id == 1 and users.users[1].name == nil)
assert(users.users[2].id == 2)Arguments
| Name | Type | Description |
|---|---|---|
shape | any? | the selection applied to each member, or every value when omitted |
Returns
| Type | Description |
|---|---|
table | an array pull shape |
asArrayfunction#
Marks a Lua table as a JSON array, including while it is empty.
Type parameters
| Name | Description |
|---|---|
T |
Arguments
| Name | Type | Description |
|---|---|---|
takes value | T | the table to mark |
Returns
| Type | Description |
|---|---|
T preserves value | the same table |
asObjectfunction#
Marks a Lua table as a JSON object.
const json = nupp.codec.json
local object = json.asObject(json.asArray({}))
assert(json.encode(object) == "{}")Type parameters
| Name | Description |
|---|---|
T |
Arguments
| Name | Type | Description |
|---|---|---|
takes value | T | the table to mark |
Returns
| Type | Description |
|---|---|
T preserves value | the same table |
decodefunction#
Decodes one complete JSON document.
Null members are dropped unless a replacement value is supplied.
Arguments
| Name | Type | Description |
|---|---|---|
text | string | the complete JSON document |
nullValue | any? | the value used for JSON null, or nil to drop nulls |
Returns
| Type | Description |
|---|---|
any | the decoded Lua value |
decodeAsfunction#
function decodeAs<T>(type: Type<T>, text: string): T?, string?Decodes JSON into the nominal record named by a type witness.
(nupp.derive.JSON)
local record User
id: integer
end
local user, problem = nupp.codec.json.decodeAs(User, [[{"id":41}]])
assert(problem == nil and user and user.id == 41)Type parameters
| Name | Description |
|---|---|
T |
Arguments
| Name | Type | Description |
|---|---|---|
type | Type<T> | the record type to decode |
text | string | the JSON document |
Returns
| Type | Description |
|---|---|
T? | the decoded record, or nil when it does not fit |
string? | the failure reason, when unsuccessful |
Raises
when JSON was not derived for the type
encodefunction#
Serializes one Lua value as JSON.
Arguments
| Name | Type | Description |
|---|---|---|
value | any | the value to serialize |
nullValue | any? | an additional value treated as JSON null |
Returns
| Type | Description |
|---|---|
string | the JSON document |
encodeAsfunction#
function encodeAs<T>(type: Type<T>, borrows value: T): stringEncodes a nominal record named by a type witness into a new string.
(nupp.derive.JSON)
local record User
id: integer
end
local text = nupp.codec.json.encodeAs(User, new User(id = 41))
assert(text == [[{"id":41}]])Type parameters
| Name | Description |
|---|---|
T |
Arguments
| Name | Type | Description |
|---|---|---|
type | Type<T> | the record type being encoded |
borrows value | T | the record value |
Returns
| Type | Description |
|---|---|
string | the JSON document |
Raises
when JSON was not derived for the type
encodedfunction#
function encoded(value: any, nullValue: any?): EncodedValueEncodes a Lua value once for trusted raw insertion by a Writer.
The returned handle retains immutable encoded bytes. Each later write copies those bytes directly to its destination without walking or validating the value.
Arguments
| Name | Type | Description |
|---|---|---|
value | any | the value to encode |
nullValue | any? | an additional value treated as JSON null |
Returns
| Type | Description |
|---|---|
EncodedValue | the reusable encoded value |
encodedStringfunction#
function encodedString(value: string): EncodedStringEncodes and interns one string for trusted use as either a key or value.
local key = nupp.codec.json.encodedString("user_id")Arguments
| Name | Type | Description |
|---|---|---|
value | string | the unencoded string |
Returns
| Type | Description |
|---|---|
EncodedString | its reusable encoded representation |
encodeRecordfunction#
function encodeRecord<T>(borrows value: T): stringEncodes a nominal record using its own metatable into a new string.
(nupp.derive.JSON)
local record User
id: integer
end
local text = nupp.codec.json.encodeRecord(new User(id = 41))
assert(text == [[{"id":41}]])Type parameters
| Name | Description |
|---|---|
T |
Arguments
| Name | Type | Description |
|---|---|---|
borrows value | T | the record value |
Returns
| Type | Description |
|---|---|
string | the JSON document |
Raises
when JSON was not derived for the type
isArrayfunction#
Answers whether a value has JSON array shape.
Decoding retains container shape even when an array is empty. This predicate reads that shape without exposing the private marker used by the codec.
const json = nupp.codec.json
assert(json.isArray(json.decode("[]")))
assert(not json.isArray(json.decode("{}")))
assert(json.isArray({1, 2}))Arguments
| Name | Type | Description |
|---|---|---|
value | any | the value whose JSON container shape to inspect |
Returns
| Type | Description |
|---|---|
boolean | true for a JSON array and false for an object or scalar |
Raises
if a table mixes object and array keys or contains array holes
pullfunction#
Materializes only the fields selected by an On-Demand pull shape.
local value = nupp.codec.json.pull(
[[{"id":41,"profile":{"name":"Ada","admin":true}}]],
{id = true, profile = {name = true}}
)
assert(value.id == 41 and value.profile.name == "Ada")
assert(value.profile.admin == nil)Arguments
| Name | Type | Description |
|---|---|---|
text | string | the complete JSON document |
shape | any | the fields and array members to materialize |
nullValue | any? | the value used for JSON null, or nil to drop nulls |
Returns
| Type | Description |
|---|---|
any | the selected Lua value |
serializefunction#
Serializes one Lua value as JSON; an alias of encode.
Arguments
| Name | Type | Description |
|---|---|---|
value | any | the value to serialize |
nullValue | any? | an additional value treated as JSON null |
Returns
| Type | Description |
|---|---|
string | the JSON document |
verifiedfunction#
function verified(text: string): EncodedValueValidates existing JSON bytes once without decoding or re-encoding them.
Arguments
| Name | Type | Description |
|---|---|---|
text | string | one complete JSON value |
Returns
| Type | Description |
|---|---|
EncodedValue | a handle retaining the original immutable string |
verifiedStringfunction#
function verifiedString(text: string): EncodedStringValidates and interns existing bytes containing one complete JSON string.
local key = nupp.codec.json.verifiedString([["user_id"]])Arguments
| Name | Type | Description |
|---|---|---|
text | string | an encoded JSON string, including its quotes |
Returns
| Type | Description |
|---|---|
EncodedString | a handle retaining the original immutable string |
writeAsfunction#
Writes a nominal record after explicitly supplying its type witness.
(nupp.derive.JSON)
local record User
id: integer
end
local out = nupp.text.buffer.new()
local writer = nupp.codec.json.writer(out)
nupp.codec.json.writeAs(User, new User(id = 41), writer)
writer:close()
assert(out:tostring() == [[{"id":41}]])Type parameters
| Name | Description |
|---|---|
T |
Arguments
| Name | Type | Description |
|---|---|---|
type | Type<T> | the record type being encoded |
borrows value | T | the record value |
exclusive out | Writer | the checked destination |
Returns
| Type | Description |
|---|---|
nil |
Raises
when JSON was not derived for the type
writerfunction#
Creates an incremental JSON writer over caller-owned storage.
local out = nupp.text.buffer.new()
local writer = nupp.codec.json.writer(out)
writer:startObject():key("id"):write(41):endObject()
writer:close()
assert(out:tostring() == [[{"id":41}]])Arguments
| Name | Type | Description |
|---|---|---|
exclusive out | Buffer | the destination receiving bytes as operations complete |
nullValue | any? | an additional value treated as JSON null |
Returns
| Type | Description |
|---|---|
Writer | a new writer |
writeRecordfunction#
Writes a nominal record using its own metatable as the type witness.
(nupp.derive.JSON)
local record User
id: integer
end
local out = nupp.text.buffer.new()
local writer = nupp.codec.json.writer(out)
nupp.codec.json.writeRecord(new User(id = 41), writer)
writer:close()
assert(out:tostring() == [[{"id":41}]])Type parameters
| Name | Description |
|---|---|
T |
Arguments
| Name | Type | Description |
|---|---|---|
borrows value | T | the record value |
exclusive out | Writer | the checked destination |
Returns
| Type | Description |
|---|---|
nil |
Raises
when JSON was not derived for the type
Values#
EMPTY_ARRAYvariable#
const EMPTY_ARRAY: tableAn explicit empty JSON array.
EMPTY_OBJECTvariable#
const EMPTY_OBJECT: tableAn explicit empty JSON object.
NULLvariable#
Sentinel that decodes from and encodes as JSON null.