std/json

Json, a value with the options of the format, for encoding and decoding any Encode/Decode type, and JsonValue for the rare document whose shape is not known ahead of time.

Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite

Die generierte Referenz von std/json zeigt jede öffentliche Deklaration mit Signatur, Doc-Kommentar und Beispielen.

std/json is JSON as a format of std/encoding, written in TorbScript: Json reads and writes any Encode/Decode type, and JsonValue is a library type for the rare document whose shape is not known ahead of time, because Encoder and Decoder are driven by the type being read or written and cannot express "whatever is there" on their own. Json is in the prelude.

Import#

use Json, JsonValue, JsonError, JsonPointer, JsonPointerError, JsonPointerProblem from "std/json"
type User {
  name: String
  age: Int
}

const json = Json()
const text = json.encode User(name: "Ada", age: 36)
print text
match json.decode<User>(text) {
  Ok(user) => print user.name
  Fail(error) => print "Rejected: {error}"
}

Declarations#

Json#

type Json {
  naming: Naming = Naming.Unchanged
  strict: Bool = false
  flagsAsNames: Bool = false
  fn encode<Value: Encode>(value: Value): String
  fn decode<Value: Decode>(text: String): Result<Value, JsonError>
  fn parse(text: String): Result<JsonValue, JsonError>
  fn value<Value: Encode>(value: Value): JsonValue
}
extend Json with Format<JsonError>

A Json is the format with its options: naming spells every field name (Naming.SnakeCase writes logLabel as log_label, and reads it back), and strict makes a field of the input that no type asked for an error instead of being ignored. flagsAsNames writes a Flags as the array of its case names (["Read","Write"]) and reads it back from one, where it is a number (3, its mask) by default. encode renders any Encode value as JSON text; decode<Value> parses text and reads it as Value, and fails on malformed JSON as well as on JSON that does not fit Value, with the field chain it went wrong in. parse reads text into a JsonValue without knowing its shape; value is any Encode value as the JsonValue it would be written as, for example to diff two versions of it structurally.

What a value is written as: a record is an object in declaration order, a record that announces no field is its one value, a case with fields is {"Case":{...}} and one without is "Case", a sequence is an array, a map is an object whose keys are texts (a number key is quoted, and read back as a number), None is null, and bytes are an array of numbers. A number without a fraction or an exponent is read as a whole number, so the whole range of Int64 and of UInt64 round-trips exactly; infinity and "not a number" have no JSON and are written as null.

extend Json with Format<JsonError> additionally gives Json.items<Item>() and Json.encoded<Item>(), the two Stages that let a byte stream be decoded one element at a time (see std/stream); a [ at the start of the stream opens one top-level array, and anything else is read as newline- or concatenation-separated values, which covers NDJSON without a rule of its own.

JsonValue#

type JsonValue {
  case Null
  case Bool(value: Bool)
  case Number(value: Float64)
  case String(value: String)
  case Array(items: List<Self>)
  case Object(entries: Map<String, Self>)
}
extend JsonValue with Encode
extend JsonValue {
  fn get(pointer: JsonPointer): Result<JsonValue, JsonPointerError>
}

A JSON document without a fixed shape. JSON does not distinguish an integer from a floating-point number, so every Number carries a Float64. A JsonValue describes itself to any Encoder, not only Json's own.

JsonError#

type JsonError with Show, Error {
  static fn syntax(line: Int, message: String): Self
  static fn decodeFailed(cause: DecodeError): Self
  static fn invalidText(cause: Utf8Error): Self
  fn isSyntaxError(): Bool
  fn show(): String
  fn cause(): Error?
}
extend JsonError with From<Utf8Error>

What went wrong turning text into a value: malformed JSON text (syntax, with the line it was found on), JSON that parsed fine but did not fit the requested type (decodeFailed), or bytes that were not UTF-8 in the first place (invalidText, from std/stream's Utf8Error). isSyntaxError() tells the first case from the other two; cause() is the DecodeError or Utf8Error a rejected document failed on, so a report can unwind the chain down to the field or the byte.

JsonPointer#

type JsonPointer with Show, Equals, Hash {
  static fn root(): Self
  static fn parse(text: String): Result<Self, JsonPointerError>
  static fn parseFragment(text: String): Result<Self, JsonPointerError>
  fn text(): String
  fn fragment(): String
  fn tokens(): List<String>
  fn child(token: String): Self
  fn parent(): Self?
  fn last(): String?
  fn show(): String
}
type JsonPointerProblem with Show, Equals, Hash {
  case Syntax
  case Fragment
  case MissingMember
  case IndexOutOfRange
  case NotAnIndex
  case EndOfArray
  case NotAContainer
  fn show(): String
}
type JsonPointerError with Show, Equals, Error {
  pointer: String
  problem: JsonPointerProblem
  message: String
  fn show(): String
}

A JsonPointer is the address of one value inside a JSON document (RFC 6901): a list of reference tokens, each the name of an object member or the index of an array item. The empty list is the root, the whole document. It has two spellings that are one pointer: the string form /foo/0, where ~0 stands for a ~ and ~1 for a / inside a token, and the URI fragment form of section 6, the same text percent-encoded to sit behind a #. parse reads the first and parseFragment the second, text() and fragment() write them, and tokens() are the unescaped names and indexes. child, parent and last walk up and down without touching the pointer they are called on.

JsonValue.get follows a pointer through a document. A token on an array is a decimal index without a sign or a leading zero, and -, the position after the last item, is kept as a token but holds no value, so get refuses it. Every failure is a JsonPointerError that names the pointer text and says which JsonPointerProblem it is: not a pointer, not a fragment, a missing member, an index out of range, a token that is no index, -, or a token on a value that is not an object or array.

use Json, JsonPointer from "std/json"

const text = "\{\"users\": [\{\"name\": \"Ada\"\}, \{\"name\": \"Grace\"\}]\}"
match Json().parse(text) {
  Ok(document) => {
    match JsonPointer.parse("/users/1/name") {
      Ok(pointer) => print document.get(pointer)
      Fail(problem) => print "Not a pointer: {problem}"
    }
  }
  Fail(problem) => print "Not JSON: {problem}"
}
Esc