# `nupp.serde.json` This codec writes and reads JSON text through any `nupp.serde.Binding`. See [Serialization bindings](../../../../learn/runtime/data/serde/index.html#json-mappings) for field mappings. ```nupp 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`](#nupp.serde.json.Codec) with [`defaultPolicy`](#nupp.serde.json.defaultPolicy), which reads declaration bindings and documents. A model library passes a [`PolicyFactory`](#nupp.serde.json.PolicyFactory) of its own to [`codec`](#nupp.serde.json.codec), or [`memberNamePolicy`](#nupp.serde.json.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. ## Types ### `Codec` _interface_ ```nupp 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`](#nupp.serde.json.defaultPolicy). Hold a codec of your own to use another policy, or to bound and clear what it retains. #### Members | Name | Kind | Description | | --- | --- | --- | | [`encodeDocument`](#nupp.serde.json.Codec.encodeDocument) | method | Encodes a document's contents. | | [`decodeDocument`](#nupp.serde.json.Codec.decodeDocument) | method | Decodes document contents. | | [`writeValue`](#nupp.serde.json.Codec.writeValue) | method | Writes one value into a JSON writer. | | [`encode`](#nupp.serde.json.Codec.encode) | method | Encodes a value. | | [`encodeInto`](#nupp.serde.json.Codec.encodeInto) | method | Appends a value to a buffer. | | [`decode`](#nupp.serde.json.Codec.decode) | method | Decodes a value. | | [`decodeBuffer`](#nupp.serde.json.Codec.decodeBuffer) | method | Decodes a value from a buffer. | | [`clear`](#nupp.serde.json.Codec.clear) | method | Forgets every binding's selections. | | [`cachedBindings`](#nupp.serde.json.Codec.cachedBindings) | method | Returns how many bindings have selections retained. | #### `encodeDocument` _method_ ```nupp encodeDocument: function(self, adapter: core.DocumentAdapter, borrows value: D): string ``` Encodes a document's contents. See [`encodeDocument`](#nupp.serde.json.encodeDocument). ##### Arguments | Name | Type | Description | | --- | --- | --- | | `?` | `self` | | | `adapter` | `core.DocumentAdapter\` | | | `borrows value` | `D` | | ##### Returns | Type | Description | | --- | --- | | `string` | | #### `decodeDocument` _method_ ```nupp decodeDocument: function(self, adapter: core.DocumentAdapter, bytes: string, context: core.Context?): D ``` Decodes document contents. See [`decodeDocument`](#nupp.serde.json.decodeDocument). ##### Arguments | Name | Type | Description | | --- | --- | --- | | `?` | `self` | | | `adapter` | `core.DocumentAdapter\` | | | `bytes` | `string` | | | `context` | `core.Context?` | | ##### Returns | Type | Description | | --- | --- | | `D` | | #### `writeValue` _method_ ```nupp writeValue: function(self, binding: core.Binding, borrows value: T, exclusive sink: json.Writer): nil ``` Writes one value into a JSON writer. See [`writeValue`](#nupp.serde.json.writeValue). ##### Arguments | Name | Type | Description | | --- | --- | --- | | `?` | `self` | | | `binding` | `core.Binding\` | | | `borrows value` | `T` | | | `exclusive sink` | `json.Writer` | | ##### Returns | Type | Description | | --- | --- | | `nil` | | #### `encode` _method_ ```nupp encode: function(self, binding: core.Binding, borrows value: T): string ``` Encodes a value. See [`encode`](#nupp.serde.json.encode). ##### Arguments | Name | Type | Description | | --- | --- | --- | | `?` | `self` | | | `binding` | `core.Binding\` | | | `borrows value` | `T` | | ##### Returns | Type | Description | | --- | --- | | `string` | | #### `encodeInto` _method_ ```nupp encodeInto: function(self, binding: core.Binding, borrows value: T, exclusive buffer: text.Buffer): nil ``` Appends a value to a buffer. See [`encodeInto`](#nupp.serde.json.encodeInto). ##### Arguments | Name | Type | Description | | --- | --- | --- | | `?` | `self` | | | `binding` | `core.Binding\` | | | `borrows value` | `T` | | | `exclusive buffer` | `text.Buffer` | | ##### Returns | Type | Description | | --- | --- | | `nil` | | #### `decode` _method_ ```nupp decode: function(self, binding: core.Binding, bytes: string, context: core.Context?): T ``` Decodes a value. See [`decode`](#nupp.serde.json.decode). ##### Arguments | Name | Type | Description | | --- | --- | --- | | `?` | `self` | | | `binding` | `core.Binding\` | | | `bytes` | `string` | | | `context` | `core.Context?` | | ##### Returns | Type | Description | | --- | --- | | `T` | | #### `decodeBuffer` _method_ ```nupp decodeBuffer: function(self, binding: core.Binding, exclusive input: text.Buffer, context: core.Context?): T ``` Decodes a value from a buffer. See [`decodeBuffer`](#nupp.serde.json.decodeBuffer). ##### Arguments | Name | Type | Description | | --- | --- | --- | | `?` | `self` | | | `binding` | `core.Binding\` | | | `exclusive input` | `text.Buffer` | | | `context` | `core.Context?` | | ##### Returns | Type | Description | | --- | --- | | `T` | | #### `clear` _method_ ```nupp clear: function(self): nil ``` Forgets every binding's selections. ##### Arguments | Name | Type | Description | | --- | --- | --- | | `?` | `self` | | ##### Returns | Type | Description | | --- | --- | | `nil` | | #### `cachedBindings` _method_ ```nupp cachedBindings: function(self): integer ``` Returns how many bindings have selections retained. ##### Arguments | Name | Type | Description | | --- | --- | --- | | `?` | `self` | | ##### Returns | Type | Description | | --- | --- | | `integer` | | ### `FieldName` _record_ ```nupp record FieldName is core.WireField ... ``` The wire field a JSON decoder reports for a member the model does not declare. #### Members | Name | Kind | Description | | --- | --- | --- | | [`name`](#nupp.serde.json.FieldName.name) | field | The member name from the JSON object, with its escapes decoded. | | [`diagnosticLabel`](#nupp.serde.json.FieldName.diagnosticLabel) | method | Returns the name, for messages such as an unknown member's. | #### `name` _field_ ```nupp name: string ``` `@readonly` The member name from the JSON object, with its escapes decoded. #### `diagnosticLabel` _method_ ```nupp diagnosticLabel: function diagnosticLabel(self): string ``` Returns the name, for messages such as an unknown member's. ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `any` | | ##### Returns | Type | Description | | --- | --- | | `string` | the member name | ### `Policy` _interface_ ```nupp interface Policy ... ``` 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 | Name | Description | | --- | --- | | `M` | | #### Members | Name | Kind | Description | | --- | --- | --- | | [`name`](#nupp.serde.json.Policy.name) | method | Returns the JSON member name of a structure or union member. | | [`select`](#nupp.serde.json.Policy.select) | method | Returns whether a member is traversed, omitted, or redacted. | | [`timestamp`](#nupp.serde.json.Policy.timestamp) | method | Encodes a timestamp member's value as JSON. | | [`parseTimestamp`](#nupp.serde.json.Policy.parseTimestamp) | method | Decodes a timestamp member's JSON value. | #### `name` _method_ ```nupp 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 | Name | Type | Description | | --- | --- | --- | | `?` | `self` | | | `member` | `M` | the member to name | ##### Returns | Type | Description | | --- | --- | | `string` | the JSON name | #### `select` _method_ ```nupp 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 | Name | Type | Description | | --- | --- | --- | | `?` | `self` | | | `member` | `M` | the member to select | | `direction` | `core.Direction` | whether the plan is for writing or reading | ##### Returns | Type | Description | | --- | --- | | `Selection` | the selection | #### `timestamp` _method_ ```nupp timestamp: function(self, member: M, borrows value: core.Timestamp): json.EncodedValue ``` Encodes a timestamp member's value as JSON. ##### Arguments | Name | Type | Description | | --- | --- | --- | | `?` | `self` | | | `member` | `M` | the member being written | | `borrows value` | `core.Timestamp` | the timestamp | ##### Returns | Type | Description | | --- | --- | | `json.EncodedValue` | the encoded JSON value, such as an RFC 3339 string or a number of epoch seconds | #### `parseTimestamp` _method_ ```nupp 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 | Name | Type | Description | | --- | --- | --- | | `?` | `self` | | | `member` | `M` | the member being read | | `text` | `string` | the string's contents, or the number's lexeme | | `kind` | `"string" | "number"` | which of the two the input held | ##### Returns | Type | Description | | --- | --- | | `core.Timestamp` | the timestamp | ### `PolicyFactory` _interface_ ```nupp interface PolicyFactory ... ``` Returns the [`Policy`](#nupp.serde.json.Policy) for a binding's member type. #### Members | Name | Kind | Description | | --- | --- | --- | | [`policy`](#nupp.serde.json.PolicyFactory.policy) | method | Returns the policy for a binding whose value sits at a member. | #### `policy` _method_ ```nupp policy: function(self, member: M): Policy ``` 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 | Name | Type | Description | | --- | --- | --- | | `?` | `self` | | | `member` | `M` | the member the binding's root value sits at | ##### Returns | Type | Description | | --- | --- | | `Policy\` | the policy | ### `Selection` _type_ ```nupp type Selection = "traverse" | "omit" | "redact" ``` Whether a policy writes a member, leaves it out, or writes it redacted. ## Functions ### `codec` _function_ ```nupp function codec(policy: plans.PolicyFactory?, capacity: integer?): Codec ``` Makes a [`Codec`](#nupp.serde.json.Codec). ```nupp 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 | Name | Type | Description | | --- | --- | --- | | `policy` | `plans.PolicyFactory?` | the policy; nil is [`defaultPolicy`](#nupp.serde.json.defaultPolicy) | | `capacity` | `integer?` | the most bindings to retain selections for; nil is 256 | #### Returns | Type | Description | | --- | --- | | `Codec` | the codec | ### `decode` _function_ ```nupp function decode(binding: core.Binding, 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. ```nupp 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 | Name | Description | | --- | --- | | `T` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `binding` | `core.Binding\` | the binding to construct through | | `bytes` | `string` | the JSON text | | `context` | `core.Context?` | a parent for the context the decoder attaches, or nil | #### Returns | Type | Description | | --- | --- | | `T` | the value | #### Raises | Type | Condition | | --- | --- | | `nupp.serde.Error` | with the path and byte offset of the failure | ### `decodeBuffer` _function_ ```nupp function decodeBuffer(binding: core.Binding, exclusive input: text.Buffer, context: core.Context?): T ``` Decodes a caller-owned Buffer, reading it in place when the provider can. #### Type parameters | Name | Description | | --- | --- | | `T` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `binding` | `core.Binding\` | the binding to construct through | | `exclusive input` | `text.Buffer` | the buffer holding the JSON text | | `context` | `core.Context?` | a parent for the context the decoder attaches, or nil | #### Returns | Type | Description | | --- | --- | | `T` | the value | #### Raises | Type | Condition | | --- | --- | | `nupp.serde.Error` | with the path and byte offset of the failure | ### `decodeDocument` _function_ ```nupp function decodeDocument(adapter: core.DocumentAdapter, bytes: string, context: core.Context?): D ``` Decodes JSON text as document contents, attaching the context it was read under. #### Type parameters | Name | Description | | --- | --- | | `D` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `adapter` | `core.DocumentAdapter\` | the document adapter, such as `nupp.serde.document.adapter` | | `bytes` | `string` | the JSON text | | `context` | `core.Context?` | a parent for the attached context, or nil | #### Returns | Type | Description | | --- | --- | | `D` | the document | #### Raises | Type | Condition | | --- | --- | | `nupp.serde.Error` | with the path and byte offset of the failure | ### `documentPolicy` _function_ ```nupp function documentPolicy(member: M, epochTimestamps: boolean): plans.Policy? ``` 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`](#nupp.serde.json.PolicyFactory) asks this first, so documents nested in its values are read and written the way [`defaultPolicy`](#nupp.serde.json.defaultPolicy) reads them. #### Type parameters | Name | Description | | --- | --- | | `M` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `member` | `M` | the member a codec asks about | | `epochTimestamps` | `boolean` | whether document timestamps are written as epoch seconds | #### Returns | Type | Description | | --- | --- | | `plans.Policy\?` | the policy, or nil | ### `encode` _function_ ```nupp function encode(binding: core.Binding, borrows value: T): string ``` Encodes a value as JSON text. ```nupp 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 | Name | Description | | --- | --- | | `T` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `binding` | `core.Binding\` | the value's binding | | `borrows value` | `T` | the value | #### Returns | Type | Description | | --- | --- | | `string` | the JSON text | #### Raises | Type | Condition | | --- | --- | | `nupp.serde.Error` | when a value cannot be represented, at its path | ### `encodeDocument` _function_ ```nupp function encodeDocument(adapter: core.DocumentAdapter, borrows value: D): string ``` Encodes a document's contents as JSON text. #### Type parameters | Name | Description | | --- | --- | | `D` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `adapter` | `core.DocumentAdapter\` | the document adapter, such as `nupp.serde.document.adapter` | | `borrows value` | `D` | the document | #### Returns | Type | Description | | --- | --- | | `string` | the JSON text | #### Raises | Type | Condition | | --- | --- | | `nupp.serde.Error` | when a value has no JSON representation | ### `encodeInto` _function_ ```nupp function encodeInto(binding: core.Binding, 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 | Name | Description | | --- | --- | | `T` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `binding` | `core.Binding\` | the value's binding | | `borrows value` | `T` | the value | | `exclusive buffer` | `text.Buffer` | the buffer to append to | #### Returns | Type | Description | | --- | --- | | `nil` | | #### Raises | Type | Condition | | --- | --- | | `nupp.serde.Error` | when a value cannot be represented, at its path | ### `fieldName` _function_ ```nupp function fieldName(field: core.WireField): string ``` Returns the JSON member name of a wire field. #### Arguments | Name | Type | Description | | --- | --- | --- | | `field` | `core.WireField` | | #### Returns | Type | Description | | --- | --- | | `string` | | ### `writeValue` _function_ ```nupp function writeValue(binding: core.Binding, 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. ```nupp 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 | Name | Description | | --- | --- | | `T` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `binding` | `core.Binding\` | the value's binding | | `borrows value` | `T` | the value | | `exclusive sink` | `json.Writer` | the writer, positioned where a value may go | #### Returns | Type | Description | | --- | --- | | `nil` | | #### Raises | Type | Condition | | --- | --- | | `nupp.serde.Error` | when a value cannot be represented, at its path | ## Values ### `defaultPolicy` _variable_ ```nupp const defaultPolicy: plans.PolicyFactory ``` 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. ### `identity` _variable_ ```nupp const identity: table ``` 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. ### `memberNamePolicy` _variable_ ```nupp const memberNamePolicy: plans.PolicyFactory ``` The policy that writes every member under its own `name`, traverses every member, and writes timestamps as RFC 3339 strings. [`defaultPolicy`](#nupp.serde.json.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. ```nupp 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 = new serde.Bound( 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"') ```