#Json
Status: standard library specification draft.
This chapter defines std.json, which lib/std writes in ordinary hd
over the language tier:
- the value type
Json, andNumber, which keeps 64-bit integers exact; parse, a strict RFC 8259 parser;JsonError, the error thatparseanddecodereturn;- the compact
Displaytext of aJson, and itsprettymethod; - the typed part: JSON as a format over opt-in serialization, with
to_json,from_json,encode,encode_pretty, anddecode.
Nothing in it is language tier.
- std-json.import.core
std.jsondeclaresJson,Number,JsonError, andparse. None is a prelude name; code imports them, as inuse std.json.parse. - std-json.import.format
std.jsonalso declaresto_json,from_json,encode, anddecode. None is a prelude name either. - std-json.import.pretty
std.jsonalso declaresencode_pretty, which is not a prelude name. - std-json.no-panicNone of these functions panics.
use std.json.{Json, JsonError, parse}
fn read(text: string) -> Result[Json, JsonError]:
parse(text) # a bad text is an .Err: no call here panics#Json Values
A Json is one of six kinds:
pub enum Json:
Null
Bool(value: bool)
Number(value: Number)
Text(value: string)
Array(items: List[Json])
Object(fields: JsonObject)- std-json.value.decl
std.jsondeclares the enumJsonwith exactly the variants above. - std-json.value.object-typeAn
Objectholds aJsonObject, which keeps its members in insertion order. It is not aMap, whose iteration order is unspecified. - std-json.value.eq
JsonimplementsEq. Two values are equal when they are the same variant with equal contents. - std-json.value.eq.arrayArrays are equal when they have equal items in the same order.
- std-json.value.eq.objectObjects are equal when they have the same keys with equal values. Their order does not matter.
- std-json.value.debug
Json,Number, andJsonErrorimplementDebug, asstd-format.debug.std-typesrequires.
The accessors read one kind and give .None for any other:
| Rule | Method | Result |
|---|---|---|
std-json.value.accessors.getget |
pub fn get(self, key: string) -> Json? |
the value of key when self is an Object with that key |
std-json.value.accessors.at-indexat |
pub fn at(self, index: usize) -> Json? |
the item at index when self is an Array and index is from 0 up to its length |
std-json.value.accessors.textas_text |
pub fn as_text(self) -> string? |
the string of a Text |
std-json.value.accessors.boolas_bool |
pub fn as_bool(self) -> bool? |
the value of a Bool |
std-json.value.accessors.nullis_null |
pub fn is_null(self) -> bool |
true exactly for Null |
std-json.value.accessors.i64as_i64 |
pub fn as_i64(self) -> i64? |
what Number.as_i64 gives for a Number |
std-json.value.accessors.u64as_u64 |
pub fn as_u64(self) -> u64? |
what Number.as_u64 gives for a Number |
std-json.value.accessors.f64as_f64 |
pub fn as_f64(self) -> f64? |
what Number.as_f64 gives for a Number |
- std-json.value.accessorsThese methods are declared on
Jsoninstd.json. A negativeindexgives.None. - std-json.value.accessors.integer-exact
as_i64andas_u64give.Nonefor a float, even one with no fraction, such as1.0, as an integer type'sfrom_jsondoes.
Json |
as_i64() |
as_u64() |
as_f64() |
|---|---|---|---|
-3 |
.Some(-3) |
.None |
.Some(-3.0) |
18446744073709551615 |
.None |
.Some(18446744073709551615) |
.Some of the nearest f64, 2^64 |
2.0 |
.None |
.None |
.Some(2.0) |
"2" |
.None |
.None |
.None |
use std.json.Json
fn count(value: Json) -> i64:
value.as_i64().unwrap_or(0) # 0 for 2.0: a float is not an integerNote. There is no accessor for the
Numberitself:match value: .Number(n) => ...gives it. The accessors take no default. Chain them withand_then(Option), or reach a nested value withpointer.
#JSON Pointer
pointer reaches a nested value in one call, by a JSON Pointer
(RFC 6901):
| Rule | Method | Result |
|---|---|---|
std-json.pointerpointer |
pub fn pointer(self, path: string) -> Json? |
the value that path points to from self, or .None when there is none |
- std-json.pointer.syntaxThe path
""points toself. Any other path starts with/, and each/begins one token, which runs to the next/or the end. A path that does not start with/gives.None. - std-json.pointer.escapesIn a token,
~1reads as/and~0as~, from the left. Any other~stays as written, as inserde_json. - std-json.pointer.stepEach token steps into the current value: an
Object's member whose key is the token, or anArray's item. Any other value, or a key that is not there, gives.None. - std-json.pointer.indexAn
Arraytoken is base-ten digits with no sign and no leading zero, and its value must be below the array's length. Any other token,-among them, gives.None.
pointer of {"db":{"pool":[5,6]},"a/b":1,"m~n":2,"":3} |
Result |
|---|---|
"" |
.Some of the whole value |
"/db/pool/1" |
.Some(6) |
"/a~1b", "/m~0n" |
.Some(1), .Some(2) |
"/" |
.Some(3): the key "" |
"/db/pool/01", "/db/pool/2", "/db/pool/-" |
.None |
"db" |
.None: no leading / |
use std.json.{JsonError, parse}
fn pool_max(text: string) -> Result[i64, JsonError]:
doc := parse(text)?
.Ok(doc.pointer("/db/pool/max").and_then(fn(found): found.as_i64()).unwrap_or(10))Why. RFC 6901 is the one path syntax JSON tools share, and
serde_jsonand Go's JSON Pointer packages read it. A.Nonefor any miss keeps one call for the common lookup, asgetdoes.
#Json Objects
JsonObject is the type of an Object's members. Insertion order is part
of its contract, so a config file parsed and written again keeps its keys
where they were.
| Rule | Method | Result |
|---|---|---|
std-json.object.new-valuenew |
pub fn new() -> JsonObject |
an object with no members |
std-json.object.lenlen |
pub fn len(self) -> usize |
the number of members; is_empty is len() == 0 |
std-json.object.getget |
pub fn get(self, key: string) -> Json? |
the value of key, or .None; contains_key is true for a key that has a member |
std-json.object.set-refset |
pub fn set(&mut self, key: string, value: Json) -> void |
a new key becomes the last member, and an existing key keeps its place with the new value |
std-json.object.remove-refremove |
pub fn remove(&mut self, key: string) -> Json? |
removes the member key and gives its value, or .None when there is no such key; the other members keep their order |
std-json.object.keyskeys |
pub fn keys(self) -> List[string] |
the keys, in insertion order |
- std-json.object.decl-struct
std.jsondeclares the structJsonObject, with private fields and the methods above.JsonObjectimplementsIterable[(string, Json)],Eq, andDebug. - std-json.object.order
keys, iteration, andDebuggive the members in insertion order. - std-json.object.eqTwo
JsonObjects are equal when they have the same keys with equal values, in any order. - std-json.object.valueA
JsonObjectis a value. Copying it, or aJsonthat holds it, copies its members, sosetandremovechange only the place they are called on.
use std.json.{Json, JsonObject}
fn build() -> Json:
let mut fields = JsonObject::new()
fields.set("name", Json.Text("hd"))
fields.set("id", Json.Null)
fields.set("name", Json.Bool(true)) # keeps its place: keys() is ["name", "id"]
Json.Object(fields)#Numbers
A Number is a JSON number whose representation is private. It follows
Rust's serde_json::Number: integers in the i64 and u64 ranges are
exact, and every other number is an f64.
use std.json.Number
fn big() -> Number:
Number::from_u64(18446744073709551615)
fn half() -> Number?:
Number::from_f64(0.5)- std-json.number.decl-struct
std.jsondeclares the structNumber, a value with no public member. - std-json.number.reprA
Numberholds an integer below zero, an integer from zero up, or a finitef64. One integer has one representation, whichever constructor made it.
| Rule | Function | Result |
|---|---|---|
std-json.number.constructors.i64from_i64 |
pub fn from_i64(value: i64) -> Number |
the integer value |
std-json.number.constructors.u64from_u64 |
pub fn from_u64(value: u64) -> Number |
the integer value |
std-json.number.constructors.f64from_f64 |
pub fn from_f64(value: f64) -> Number? |
the float value, or .None for a NaN or an infinity |
- std-json.number.constructorsThese three are static functions of
Number, called asNumber::from_i64(value).
| Rule | Method | Result |
|---|---|---|
std-json.number.accessors.i64as_i64 |
pub fn as_i64(self) -> i64? |
the integer when it is in the i64 range, and .None for a float |
std-json.number.accessors.u64as_u64 |
pub fn as_u64(self) -> u64? |
the integer when it is from zero up, and .None for a negative integer or a float |
std-json.number.accessors.f64as_f64 |
pub fn as_f64(self) -> f64? |
.Some of the float, or of the integer converted to the nearest f64 |
- std-json.number.accessorsThese three are methods of
Number.as_f64gives.Somefor everyNumber, so no.Noneresult means a number is out of range.
#Number Equality
- std-json.number.eq
NumberimplementsEq, with the rule ofserde_json. Two numbers are equal when both are integers with the same value, or both are floats that are equal asf64values. - std-json.number.eq.mixedAn integer is never equal to a float, so
1and1.0differ. - std-json.number.eq.zeroThe floats
0.0and-0.0are equal, asf64values are.
use std.json.Number
fn is_integer_two(n: Number) -> bool:
n == Number::from_i64(2) # false when n is the float 2.0#Number Text
- std-json.number.display
NumberimplementsDisplay. An integer prints in base ten, astypes.display.intgives, and a float prints asf64does (types.display.float).
use std.json.Number
fn show(n: Number) -> string:
"$n" # an integer prints in base ten, a float as an f64Note. The text of a finite float is always valid JSON. It has a decimal point or an exponent, as
1.0and1e+21, and negative zero is-0.0(types.display.fixed).
Why.
serde_jsonkeeps three representations for the same reason. Au64abovei64::MAXand ani64below zero must survive a round trip that anf64would round. A JSON number is a decimal text, so a library that read every number as anf64would lose the low bits of an identifier.
#Parsing
parse reads one JSON text:
use std.json.{parse, Json, JsonError}
fn title(text: string) -> string:
match parse(text):
.Ok(value) => value.get("title").and_then(fn(found): found.as_text()).unwrap_or("")
.Err(error) => "$error"| Rule | Function | Result |
|---|---|---|
std-json.parseparse |
pub fn parse(text: string) -> Result[Json, JsonError] |
.Ok of the value that text writes, or .Err of the first error |
#Grammar
- std-json.parse.grammar
parseaccepts exactly the texts that the grammar of RFC 8259 gives for a JSON text, with any value at the top. - std-json.parse.whitespaceWhitespace is a space, a tab, a line feed, or a carriage return. It may stand before and after the value, and between any two tokens.
- std-json.parse.strictThe grammar has no extension: no comment, no trailing comma, no single-quoted string, no
NaNorInfinity, and no byte order mark. - std-json.parse.endOnly whitespace may follow the value. The first other character is an
UnexpectedCharactererror. - std-json.parse.depthAn array or object nested inside 128 others is an error, and one nested inside 127 is not. The error is
NestingTooDeep, at its opening bracket. - std-json.parse.literalsThe words
true,false, andnullare lowercase only.
use std.json.parse
fn accepts(text: string) -> bool:
match parse(text):
.Ok(_) => true
.Err(_) => false
fn checks() -> List[bool]:
[accepts("[1, 2]"), accepts("[1, 2,]"), accepts("[1, /* c */ 2]")]
# [true, false, false]: no trailing comma, no comment#Objects
- std-json.parse.object.source-orderThe members of a parsed object are in the order of their first occurrence in the text.
- std-json.parse.object.duplicate-lastWhen a key occurs more than once, the object has one entry for it. Its value is the last one, and its place is the first one's.
- std-json.parse.object.keyA key is a string, and its escapes are decoded as a string's are.
use std.json.parse
fn last_wins() -> string:
match parse("{\"b\": 1, \"a\": 2, \"b\": 3}"):
.Ok(value) => "${value}" # {"b":3,"a":2}: first place, last value
.Err(_) => "error"#Strings
- std-json.parse.string.escapesAn escape is a backslash and one of the following, and any other letter after a backslash is an
InvalidEscapeerror.
| Escape | Value |
|---|---|
\", \\, \/ |
the character after the backslash |
\b, \f, \n, \r, \t |
U+0008, U+000C, U+000A, U+000D, U+0009 |
\u and four hex digits |
the UTF-16 code unit of those digits, in either case |
- std-json.parse.string.hexA
\unot followed by four hex digits is anInvalidEscapeerror at its backslash. When the text ends first, the error isUnexpectedEnd. - std-json.parse.string.surrogatesA high surrogate escape, from
\ud800to\udbff, must be followed at once by a low surrogate escape, from\udc00to\udfff. The pair is one scalar value. - std-json.parse.string.lone-surrogateA surrogate escape with no partner is a
LoneSurrogateerror at the backslash of the first escape of the pair. - std-json.parse.string.controlA raw character below U+0020 inside a string is a
ControlCharactererror. A raw DEL (U+007F) and every other scalar value stand for themselves. - std-json.parse.string.nul
\u0000is valid, and the string then holds U+0000.
use std.json.parse
fn decoded() -> string:
match parse("\"a\\u0041\\n\""):
.Ok(value) => value.as_text().unwrap_or("") # "aA" and a line feed
.Err(_) => ""#Number Syntax
- std-json.parse.number.grammarA number is
-, then0or a nonzero digit and digits, then an optional fraction, then an optional exponent. A character that breaks this is anUnexpectedCharactererror, orUnexpectedEndat the end of the text. - std-json.parse.number.integerAn integer in the
i64range or in theu64range is exact, asNumber.as_i64orNumber.as_u64reads it. - std-json.parse.number.negative-zero
-0is the float-0.0, as inserde_json, and0is the integer zero. - std-json.parse.number.float-via-parseA number with a fraction or an exponent, or an integer outside both ranges, is the float that
text.parse::[f64]()gives for its text. - std-json.parse.number.nonfinite-parse-resultA float past the finite
f64range, whichf64parsing gives as an infinity, is aNumberOutOfRangeerror. The error sits at the number's first character, once its grammar is read to the end.
| Text | parse gives |
|---|---|
"1.5" |
.Ok of the float 1.5 |
"18446744073709551616" |
.Ok of the float 2^64, one past the u64 range |
"1e-400" |
.Ok of the float 0.0 |
"1e400" |
.Err(NumberOutOfRange(0)) |
Why.
serde_jsonreads numbers the same way. An integer in range is exact, and every other number is the nearestf64. A number past thef64range is an error, since aNumberholds no infinity (std-json.number.repr).
use std.json.parse
fn read_number(text: string) -> string:
match parse(text):
.Ok(.Number(n)) => "$n"
_ => "not a number"
fn demos() -> List[string]:
[read_number("1.5"), read_number("-0"), read_number("18446744073709551616")]
# ["1.5", "-0.0", "18446744073709552000.0"]#Error Order
- std-json.parse.error-order
parsereads the text from the left. Its error is the first position at which no continuation is valid, with the kind that the position gives.
| Text | parse gives |
|---|---|
"" |
.Err(UnexpectedEnd(0)) |
"[1,]" |
.Err(UnexpectedCharacter(3)) |
"01" |
.Err(UnexpectedCharacter(1)) |
"\"\\ud800\"" |
.Err(LoneSurrogate(1)) |
"1e400" |
.Err(NumberOutOfRange(0)) |
"1.x" |
.Err(UnexpectedCharacter(2)) |
use std.json.parse
fn first_error(text: string) -> string:
match parse(text):
.Ok(_) => "ok"
.Err(error) => "$error"
fn demo() -> string:
first_error("[1,]") # unexpected character at position 3#JSON Errors
A failed parse reports one of seven errors, each at a position. A failed decode reports a parse error, or one of three errors at a path:
pub enum JsonError:
UnexpectedEnd(position: usize)
UnexpectedCharacter(position: usize)
InvalidEscape(position: usize)
LoneSurrogate(position: usize)
ControlCharacter(position: usize)
NumberOutOfRange(position: usize)
NestingTooDeep(position: usize)
WrongType(path: string, expected: string)
MissingField(path: string)
UnknownVariant(path: string, name: string)- std-json.error.variants
std.jsondeclares the enumJsonErrorwith the ten variants above. The first seven are parse errors, and the last three are decode errors. - std-json.error.position-bytesA
positionis a byte offset into the text, counted in bytes of its UTF-8 encoding from 0, asslicecounts. - std-json.error.traits
JsonErrorimplementsEq,Debug, andDisplay.
| Rule | Variant | Position |
|---|---|---|
std-json.error.kind.endUnexpectedEnd |
the text stops before the value is complete | the length of the text |
std-json.error.kind.characterUnexpectedCharacter |
a character that the grammar does not allow there | that character |
std-json.error.kind.escapeInvalidEscape |
a backslash escape that is not in the table, or a \u without four hex digits |
the backslash |
std-json.error.kind.surrogateLoneSurrogate |
a surrogate escape with no partner | the backslash of the first escape |
std-json.error.kind.controlControlCharacter |
a raw character below U+0020 in a string | that character |
std-json.error.kind.rangeNumberOutOfRange |
a number past the finite f64 range |
its first character |
std-json.error.kind.depthNestingTooDeep |
an array or object past 128 levels | its opening bracket |
Note. A position is a valid
slicebound, sotext.slice(0, position)is the text before the error, even after a non-ASCII character.
#Decode Errors
A decode error names the value that failed by its path from the root, as
$.users[2].name:
| Rule | When |
|---|---|
std-json.error.kind.typeWrongType |
a value of another kind than the type reads, or a number outside the type's range; expected names what the type reads |
std-json.error.kind.missingMissingField |
an object without the key of a member whose type does not read null; path ends with that key |
std-json.error.kind.variantUnknownVariant |
a variant name that the enum does not declare; path is the enum value's, and name is the name read |
- std-json.error.pathA
pathis$, then one segment per step from the root value. A segment is.keyfor an object's member or a variant's payload, and[index]for an array item. - std-json.error.path.indexAn index is written in base ten, counted from 0.
- std-json.error.path.keyA key is written as it is, with no quotes and no escapes.
- std-json.error.display.pathThe
Displaytext of a decode error contains itspath.
The expected of a WrongType depends on the type that reads the value:
| Rule | Type | expected |
|---|---|---|
| std-json.error.expected.scalarScalars | bool, char, string, and each integer and float type |
the type's name, as "u8" |
| std-json.error.expected.arrayLists | List[T] |
"array" |
| std-json.error.expected.objectObjects | Map[string, V], a derived struct, and a derived variant's payload |
"object" |
| std-json.error.expected.variantEnums | a derived enum | "variant" |
Note. A path is for people to read. A key that holds
.or[makes it ambiguous, as inserde_path_to_error.
use std.json.{decode, JsonError}
use std.serde.{Serialize, Deserialize}
@derive(Serialize, Deserialize)
struct User:
name: string
age: i32
fn load(text: string) -> Result[User, JsonError]:
decode::[User](text)
fn report(text: string) -> string:
match load(text):
.Ok(user) => user.name
.Err(error) => "$error" # "{\"name\": \"ada\"}" misses $.age#Writing
A Json writes as JSON text in two layouts, compact and pretty:
use std.json.{parse, Json}
fn show(value: Json) -> string:
"${value}" # {"a":[1,2]}
fn show_pretty(value: Json) -> string:
value.pretty()| Rule | Method | Result |
|---|---|---|
std-json.value.prettypretty |
pub fn pretty(self) -> string |
the pretty text of self |
Why. Text output of a value is a method, as
to_rfc3339()andto_fixed(n)are. One spelling is enough, so there is no freeprettyfunction.
#Compact Text
- std-json.display.compact
JsonimplementsDisplay. Its text has no whitespace outside strings:[1,2]and{"a":1}. - std-json.display.kinds
Nullprints asnull, aBoolastrueorfalse, and aNumberasstd-json.number.displaygives. - std-json.display.containersAn
Arrayprints its items in order, and anObjectprints its members as"key":value, each list separated by,. - std-json.display.object-orderAn
Objectprints its members in insertion order, in compact text and inprettytext. Printing a parsed object gives its keys in the text's order. - std-json.display.textA
Textand each key print in quotes. A quote, a backslash, and each character below U+0020 are escaped, and every other scalar value prints as itself. - std-json.display.escapesThe escapes are
\",\\,\b,\f,\n,\r, and\t, and\u00with two lowercase hex digits for the other controls./is not escaped.
use std.json.{Json, parse}
fn compact(value: Json) -> string:
"$value" # no whitespace outside strings: [1,2] and {"a":1}
fn demo() -> string:
match parse("{ \"a\" : [ 1, 2 ] }"):
.Ok(value) => compact(value) # {"a":[1,2]}
.Err(_) => "error"Note. The compact text of a value that
parseread parses back to an equal value.
#Pretty Text
- std-json.pretty.layout
prettywrites the compact text's tokens with each item of an array and each entry of an object on its own line. Each line is indented by two spaces for each level of nesting. - std-json.pretty.separatorAn entry is
"key": value, with one space after the colon. A comma follows every item or entry but the last, with no space before it. - std-json.pretty.emptyAn empty array prints as
[]and an empty object as{}, on one line. - std-json.pretty.scalarA value that is not an array or an object prints as its compact text, with no line break.
- std-json.pretty.no-trailingThe text ends with its last bracket, brace, or scalar: no trailing space, no final line break.
Json |
pretty text |
|---|---|
[1,2] |
"[\n 1,\n 2\n]" |
{"a":[]} |
"{\n \"a\": []\n}" |
use std.json.parse
fn pretty_demo(text: string) -> string:
match parse(text):
.Ok(value) => value.pretty()
.Err(_) => ""#Typed JSON
JSON is a format that reads a type's
serialization opt-in.
to_json writes any value that implements Serialize as a Json, and
from_json builds a Deserialize value from one. encode and decode
join them to JSON text:
use std.json.{encode, decode, JsonError}
use std.serde.{Serialize, Deserialize}
@derive(Serialize, Deserialize)
struct Session:
pub user: string
token: string
expires_at: i64
fn save(session: Session) -> string:
encode(session) # {"user":"ada","token":"t-81f2","expires_at":1700000000}
fn load(text: string) -> Result[Session, JsonError]:
decode::[Session](text)#Encode And Decode
| Rule | Function | Result |
|---|---|---|
std-json.to-jsonto_json |
pub fn to_json[T < Serialize](value: T) -> Json |
the Json that value's serialize writes |
std-json.from-jsonfrom_json |
pub fn from_json[T < Deserialize](value: Json) -> Result[T, JsonError] |
the T that T::deserialize reads from value, or the first error it finds |
std-json.encodeencode |
pub fn encode[T < Serialize](value: T) -> string |
the compact text of to_json(value) |
std-json.encode-prettyencode_pretty |
pub fn encode_pretty[T < Serialize](value: T) -> string |
the pretty text of to_json(value), as to_json(value).pretty() gives |
std-json.decode.directdecode |
pub fn decode[T < Deserialize](text: string) -> Result[T, JsonError] |
the T that T::deserialize reads directly from validated JSON text |
- std-json.decode.parse-errorWhen
parsefails,decodereturns its parse error and reads no value. - std-json.from-json.errors
from_jsonreturns only the decode errors:WrongType,MissingField, andUnknownVariant. - std-json.from-json.rootEach path that
from_jsonreports starts at the value it reads, whose path is$. - std-json.encode.serialize
encodeorto_jsonof a type that does not implementSerializeis an error. Error:unsatisfied-trait-bound. - std-json.decode.deserialize
decodeorfrom_jsoninto a type that does not implementDeserializeis an error. Error:unsatisfied-trait-bound. - std-json.decode.no-tree
decodereads typed values without constructing an intermediateJsontree.
use std.json.{encode, decode, JsonError}
struct Plain:
id: i32
fn show(value: Plain) -> string:
encode(value) # error: unsatisfied-trait-bound
fn load(text: string) -> Result[Plain, JsonError]:
decode::[Plain](text) # error: unsatisfied-trait-boundWhy. JSON declares no trait of its own, so a type's one opt-in serves JSON and every other format alike, as
serde_jsonreads serde'sSerialize.
use std.json.encode_pretty
use std.serde.Serialize
@derive(Serialize)
struct Point:
x: i32
y: i32
fn show(point: Point) -> string:
encode_pretty(point) # "{\n \"x\": 3,\n \"y\": 4\n}" for Point { x: 3, y: 4 }Note.
encode_prettydiffers fromencodeonly in whitespace outside strings: the keys, their order, and the escapes are the same.
#Standard Implementations
JSON writes and reads each standard implementation of Serde as below:
| Rule | Type | to_json gives |
from_json reads |
|---|---|---|---|
| std-json.std.jsonJson | Json |
the value itself | any value, as itself |
| std-json.std.boolBoolean | bool |
a Bool |
a Bool |
| std-json.std.textString | string |
a Text |
a Text |
| std-json.std.charCharacter | char |
a Text of that one character |
a Text of exactly one character |
| std-json.std.integerIntegers | i8, i16, i32, i64, u8, u16, u32, u64, and usize |
the exact integer, as Number::from_i64 or Number::from_u64 gives it |
a Number that is an integer in the type's range |
| std-json.std.floatFloats | f32 and f64 |
the Number of the value as an f64 |
any Number, as as_f64 gives it |
| std-json.std.optionalOptional | T?, where T implements the trait |
null for .None, and the payload's Json for .Some |
.None from null, and .Some of what T reads from any other value |
| std-json.std.listList | List[T], where T implements the trait |
an Array of the items' values, in order |
an Array, item by item |
| std-json.std.mapMap | Map[string, V], where V implements the trait |
an Object of the entries' values, in the map's iteration order |
an Object, entry by entry, in its key order |
- std-json.std.json.impls
std.jsonimplementsSerializeandDeserializeforJson. AJsonreads throughpeek, so it takes any value. - std-json.std.integer.readA float is never an integer, so
1.0is aWrongTypefor every integer type. So is an integer outside the type's range. - std-json.std.float-specialA NaN or an infinity has no JSON number, so its
to_jsonisnull, as inserde_json. - std-json.std.float-readAn
f32reads the nearestf32to the number'sf64. A number past the finitef32range is aWrongType. - std-json.std.wrong-kindAny other kind of value is a
WrongType, with theexpectedthat Decode Errors gives. - std-json.std.item-pathAn error inside a list item gets the segment
[index], and one inside a map value gets.key.
use std.json.{encode, decode, JsonError}
fn round_trip(names: List[string]) -> Result[List[string], JsonError]:
decode::[List[string]](encode(names))Note.
T??loses a layer:.Some(.None)writesnull, which reads back as.None. A NaN writesnull, which no float reads.
Note. Only a
Mapwithstringkeys implements the trait, bystd-serde.std.map-keys, soencodeof aMap[i32, i32]is anunsatisfied-trait-bounderror.
#Derived Serialize
JSON writes a struct whose Serialize is derived as an object with one
key per member. It writes an enum in the externally tagged form, the
variant's name with its members:
use std.json.encode
use std.serde.{Serialize, Deserialize}
@derive(Serialize, Deserialize)
enum Shape:
Circle(radius: u32)
Rect(width: i32, height: i32)
Point(i32, i32)
Empty| Value | encode text |
|---|---|
Shape.Circle(7) |
{"Circle":{"radius":7}} |
Shape.Rect(2, 3) |
{"Rect":{"width":2,"height":3}} |
Shape.Point(4, 5) |
{"Point":{"_0":4,"_1":5}} |
Shape.Empty |
"Empty" |
- std-json.derive.to-json.data-included-membersA struct gives an
Objectwith one key per included member that the derivation walks, in declaration order. The key is its wire name, and its value is the member'sJson. - std-json.derive.to-json.enum-included-membersA variant with included members gives an
Objectwith one key, the variant's name. Its value is theObjectof the variant's members, as for a struct. - std-json.derive.to-json.unit-included-membersA variant with no included member for this value gives the
Textof its name. - std-json.derive.to-json.positional-wire-nameA positional member's key is its wire name, defaulting to
_0,_1, and so on. - std-json.derive.to-json.embedded-wire-nameAn embedded member is one key using its wire name, defaulting to its type's final name, with the part's
Jsonas value. - std-json.derive.to-json.sharedShared constructor data is not a member, so it is not written.
- std-json.derive.to-json.privateA private member is written like any other, since the opt-in covers it.
Note. A member omitted with
= passin a derivation block writes no key, as Omitted Members states. Renames and conditional skips are open, in Serialization Formats.
Why. The externally tagged form is
serde's default. It needs no tag key that could clash with a member's name.
#Derived Deserialize
JSON reads the form that it writes for a derived Serialize:
Text read as a Shape |
Result |
|---|---|
"Empty" or {"Empty":{}} |
.Ok(Shape.Empty) |
{"Rect":{"height":3,"width":2,"z":0}} |
.Ok(Shape.Rect(2, 3)) |
"Rect" |
.Err(MissingField("$.Rect.width")) |
"Hexagon" |
.Err(UnknownVariant("$", "Hexagon")) |
{"Rect":3} |
.Err(WrongType("$.Rect", "object")) |
{"Rect":{},"Empty":{}} |
.Err(WrongType("$", "variant")) |
- std-json.derive.from-json.inverseA derived
Deserializereads the form that a derivedSerializewrites for the same type. - std-json.derive.from-json.dataA struct reads an
Object. Any other value is aWrongType. - std-json.derive.from-json.member-wire-nameIt reads each built member in declaration order from its wire key, ending on the first error with that wire name's path segment.
- std-json.derive.from-json.unknownA key that names no member is ignored.
- std-json.derive.from-json.missingA missing key reads as
null: the member's type readsJson.Null. When that read fails, the error is aMissingFieldat the path of the missing key. - std-json.derive.from-json.defaultA missing key of a member that declares a default gives that default, and the member is not read, so it is never a
MissingField. The serde reader reports the key absent throughis_absent. - std-json.derive.from-json.null-presentA key whose value is
nullis not missing: the member readsJson.Nulleven when it declares a default, so aT?member gets.None. - std-json.derive.from-json.enumAn enum reads a
Text, or anObjectwith exactly one key. The text or the key is the variant's name. Any other value is aWrongType. - std-json.derive.from-json.name-onlyA
Textreads as anObjectwhose one key is that text and whose value is an emptyObject. - std-json.derive.from-json.unknown-variantA name that is not a variant of the enum is an
UnknownVariantat the enum value's path. - std-json.derive.from-json.payloadThe variant's payload must be an
Object, or it is aWrongTypewith the segment.nameof the variant. Its members then read as a struct's do, under that segment.
use std.json.{decode, JsonError}
use std.serde.{Serialize, Deserialize}
@derive(Serialize, Deserialize)
enum Shape:
Circle(radius: u32)
Rect(width: i32, height: i32)
Point(i32, i32)
Empty
fn read(text: string) -> Result[Shape, JsonError]:
decode::[Shape](text)
fn shape_of(text: string) -> string:
match read(text):
.Ok(Shape.Empty) => "empty"
.Ok(_) => "a shape with members"
.Err(error) => "$error"A missing key takes the member's declared default first. With no default,
a T? member reads .None, and any other member is a MissingField:
Text read as a Server |
Result |
|---|---|
{"host":"db1"} |
.Ok(Server { host: "db1", port: 8080, banner: .Some("hi"), note: .None }) |
{"host":"db1","port":5432,"banner":null} |
port 5432, banner .None |
{"port":5432} |
.Err(MissingField("$.host")) |
{"host":"db1","port":null} |
.Err(WrongType("$.port", "u16")) |
use std.json.{decode, JsonError}
use std.serde.Deserialize
@derive(Deserialize)
struct Server:
host: string
port: u16 = 8080
banner: string? = .Some("hi")
note: string?
fn load(text: string) -> Result[Server, JsonError]:
decode::[Server](text) # {"host":"db1"} gives port 8080Note. So a member of type
T?may be missing, and reads.Noneunless it declares a default, and a member of typeJsonreadsnull. A member of any other standard type must be present or declare a default.
Why.
serdeignores unknown keys by default, so a reader accepts output from a newer writer. A missing optional reads as absent, asserde'sOptiondoes. A missing key with a declared default takes it, as serde's#[serde(default)]and Go'sjson.Unmarshalinto a filled struct do, so a config file may leave out what the type already settles. Anullis a value the text wrote, so it is read.
See also: Encoding, Num, Collections, Numeric Display, Map Lookup And Order, Serialization, Serde, Derived Hashing.