Assay
Encoding
One attribute and your type writes itself back out, to every format it reads. What follows is mostly about the two things worth knowing before you rely on that: what round-trip actually guarantees you, and what can still fail on the way out.
One switch, four writers
Section titled “One switch, four writers”@Schema(keys: .snakeCase, formats: .all, encodes: true, describes: true)struct Build: Equatable { @Validate(.min(1), .max(40)) var name: String @Validate(.range(1...99)) var version: Int var stable: Bool var notes: String? @Validate(.count(1...5)) var tags: [String]}encodes: true gives you a writer for every format in formats:, and it is quick: JSON out
measures 8.28× JSONEncoder at fifty items and 8.02× at two hundred.
JSON:
Build(name: "api", version: 2, stable: true, notes: nil, tags: ["a", "b"]){"name":"api","version":2,"stable":true,"notes":null,"tags":["a","b"]}YAML:
name: apiversion: 2stable: truenotes: nulltags: - a - bTOML:
name = "api"version = 2stable = truetags = ["a", "b"]XML:
<?xml version="1.0" encoding="UTF-8"?><Build><name>api</name><version>2</version><stable>true</stable><tags>a</tags><tags>b</tags></Build>jsonText(), yamlText(), tomlText() and xmlText() give you a String;
encodedJSON() and friends give you EncodedBytes, which owns the buffer the writer wrote
and hands it over without copying it. Use withUnsafeBytes to write it somewhere, text()
for a String, or Array(_:) when you need a plain [UInt8] and can pay for the copy. All
throw.
Note that notes is absent from every output rather than written as null. TOML has no null
at all, so omitting a nil optional is the only spelling that works everywhere you might send
it.
Round-trip is a law, with a closed exception list
Section titled “Round-trip is a law, with a closed exception list”encode, then decode, then comparetrueFor any
vproduced byparse,parse(encode(v))produces a value equal tov— except in four listed cases.
Stating it as a law with a closed list of exceptions is the point. It turns round-trip
from a property nobody tests into one with a test suite and four documented holes: a
@Fallback that fired, an @Unknown case captured without roundTrips: true, XML
placement that cannot be expressed, and two untagged union variants that accept the same
document.
What can fail on the way out
Section titled “What can fail on the way out”Encoding does not re-run your rules. It tells you what cannot be written.
Unwritable(label: "x", ratio: inf)bytes written: 26issues: ratio: cannot be represented in JSON (Infinity)nan and infinity have no JSON spelling. TOML has no null, so any null that is not an
omitted optional is reported. An @Extras key can collide with a declared one.
diagnoseEncodeJSON() is the non-throwing form, and hands you what it managed to write
alongside the issues.
A JSON Schema from the same declaration
Section titled “A JSON Schema from the same declaration”describes: true adds jsonSchema(for:):
Build.jsonSchema(for: .input).text(){ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Build", "type": "object", "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 40 }, "version": { "type": "integer", "minimum": 1, "maximum": 99 }, "stable": { "type": "boolean" }, "notes": { "type": [ "string", "null" ] }, "tags": { "type": "array", "items": { "type": "string" }, "minItems": 1, "maxItems": 5 } }, "required": [ "name", "version", "stable", "tags" ] }One renderer law, and it is worth knowing before you rely on the output: describe more
than the type accepts, never less. A rule with no exact 2020-12 keyword becomes
description prose rather than an approximate pattern, because a schema that rejects a
document the decoder would have accepted is worse than a vague one.
@Key(path:) and @XML placement are refused at expansion rather than described wrongly.
- Encoding, explained — the six semantics questions and their answers.