std.json.PullParser
class pub PullParser
A pull parser for turning a stream of bytes into JSON values.
Using a PullParser
you can parse a JSON document into a desired set of
values, without the need for any intermediate Json
values. For example,
parsing an array of integers is done as follows:
import std.json (PullParser)
let parser = PullParser.new('[10, 20]')
let values = []
parser.values(fn { Result.Ok(values.push(try parser.int)) }).get
values # => [10, 20]
For objects one can use the low-level method PullParser.keys
, but it's
recommended to use PullParser.object
when the names of the keys are known at
compile-time. For example:
import std.json (PullParser)
class Person {
let @name: String
let @age: Int
}
let parser = PullParser.new('{ "name": "Alice", "age": 42 }')
let person = Person(name: '', age: 0)
parser
.object
.string('name', fn (v) { person.name = name })
.int('age', fn (v) { person.age = v })
.parse
.get
Static methods
new
Show source codeHide source code
fn pub static new[T: Bytes](input: ref T) -> PullParser {
PullParser(input: input.bytes.peekable, offset: -1, buffer: ByteArray.new)
}
fn pub static new[T: Bytes](input: ref T) -> PullParser
Returns a new parser that parses the given Bytes
value.
Instance methods
bool
Show source codeHide source code
fn pub mut bool -> Result[Bool, Error] {
match try peek {
case Some(LOWER_T) -> {
advance
identifier('rue').map(fn (_) { true })
}
case Some(LOWER_F) -> {
advance
identifier('alse').map(fn (_) { false })
}
case Some(v) -> throw unexpected(v)
case _ -> throw missing_input
}
}
fn pub mut bool -> Result[Bool, Error]
Parses the boolean value true
or false
.
Examples
import std.json (PullParser)
PullParser.new('true').bool # => Result.Ok(true)
float
Show source codeHide source code
fn pub mut float -> Result[Float, Error] {
match number {
case Ok(Int(v)) -> Result.Ok(v.to_float)
case Ok(Float(v)) -> Result.Ok(v)
case Error(e) -> Result.Error(e)
}
}
fn pub mut float -> Result[Float, Error]
Parses a number as a Float
.
If the value in the input stream is in fact an integer (e.g. 1
instead of
1.0
or 1.2
), the value is first parsed as an Int
and then cast to a
Float
.
Examples
import std.json (PullParser)
Parser.new('1.2').float # => Result.Ok(1.2)
Parser.new('1').float # => Result.Ok(1.0)
int
Show source codeHide source code
fn pub mut int -> Result[Int, Error] {
match number {
case Ok(Int(v)) -> Result.Ok(v)
case Ok(Float(v)) -> Result.Ok(v.to_int)
case Error(e) -> Result.Error(e)
}
}
fn pub mut int -> Result[Int, Error]
Parses a number as an Int
.
If the value in the input stream is in fact a float (e.g. 1.2
instead of
just 1
), it's first pased as a Float
and then cast to an Int
.
Examples
import std.json (PullParser)
Parser.new('10').int # => Result.Ok(10)
Parser.new('1.2').int # => Result.Ok(1)
keys
Show source codeHide source code
fn pub mut keys(
value: fn (String) -> Result[Nil, Error],
) -> Result[Nil, Error] {
try expect(CURLY_OPEN, skip_whitespace: true)
loop {
match try peek {
case Some(CURLY_CLOSE) -> {
advance
break
}
case Some(DQUOTE) -> {
let key = try string
try expect(COLON, skip_whitespace: true)
try value.call(key)
try separator(CURLY_CLOSE)
}
case Some(byte) -> throw unexpected(byte)
case _ -> throw missing_input
}
}
Result.Ok(nil)
}
fn pub mut keys(value: fn (String) -> Result[Nil, Error]) -> Result[Nil, Error]
Parses an object, calling value
for each key and passing it the name of
the key.
It's expected that the value
closure advances the parser by parsing the
value in it's desired format.
Examples
import std.json (PullParser)
class Person {
let @name: String
let @age: Int
}
let parser = PullParser.new('{ "name": "Alice", "age": 42 }')
let person = Person(name: '', age: 0)
parser
.keys(fn (key) {
match key {
case 'name' -> person.name = parser.string.get
case 'age' -> person.age = parser.int.get
case _ -> {}
}
Result.Ok(nil)
})
.get
person.name # => 'Alice'
person.age # => 42
null
Show source codeHide source code
fn pub mut null -> Result[Nil, Error] {
identifier('null')
}
fn pub mut null -> Result[Nil, Error]
Parses a null
value.
Examples
import std.json (PullParser)
PullParser.new('null').null # => Result.Ok(nil)
number
Show source codeHide source code
fn pub mut number -> Result[Number, Error] {
match try peek {
case Some(MINUS) -> {
try advance_and_buffer
match try peek_next {
case Some(byte) if digit?(byte) -> {}
case Some(byte) -> throw unexpected(byte)
case _ -> throw missing_input
}
}
case Some(byte) if digit?(byte) -> {}
case Some(byte) -> throw unexpected(byte)
case _ -> throw missing_input
}
# Numbers such as 001, 010, 01.0, etc are invalid.
match try peek_next {
case Some(ZERO) -> {
try advance_and_buffer
match try peek_next {
case Some(byte) if digit?(byte) -> throw unexpected(byte)
case _ -> {}
}
}
case _ -> {}
}
try buffer_digits
match try peek_next {
# Examples: 1.2, 1.2e1, 1.2e+1
case Some(DOT) -> {
try advance_and_buffer
match try peek_next {
case Some(byte) if digit?(byte) -> {}
case Some(byte) -> throw unexpected(byte)
case _ -> throw missing_input
}
try buffer_digits
match try peek_next {
case Some(byte) if exponent?(byte) -> try exponent
case _ -> {}
}
}
# Example: 1E4
case Some(byte) if exponent?(byte) -> try exponent
# Example: 123
#
# If the number is too big to fit in an Int, we'll promote it to a Float.
case _ -> {
match Int.parse(@buffer, Format.Decimal) {
case Some(val) -> {
@buffer.clear
return Result.Ok(Number.Int(val))
}
case _ -> {}
}
}
}
# At this point we've already validated the input format, and it's
# compatible with the underlying float parser, so no extra checks are
# needed.
let res = Result.Ok(Number.Float(Float.parse(@buffer).get))
@buffer.clear
res
}
fn pub mut number -> Result[Number, Error]
Parses a number into a Number
value.
A Number
is a wrapper enum around either a Float
or an Int
. If you
only care about numbers in a particular format (e.g. integers), use
PullParser.int
or PullParser.float
instead.
Examples
import std.json (PullParser)
Parser.new('1.2').number # => Result.Ok(Number.Float(1.2))
Parser.new('1').number # => Result.Ok(Number.Int(1))
object
Show source codeHide source code
fn pub mut object -> ObjectParser {
ObjectParser.new(self)
}
fn pub mut object -> ObjectParser
Returns an ObjectParser
for parsing an object with a known set of keys.
For more information, refer to the various methods of the ObjectParser
type.
Examples
import std.json (PullParser)
class Person {
let @name: String
}
let parser = PullParser.new('{ "name": "String" }')
let person = Person(name: '')
parser.object.string('name', fn (v) { person.name = v }).parse.get
offset
Show source codeHide source code
fn pub offset -> Int {
@offset
}
fn pub offset -> Int
Returns the current byte offset in the input stream.
skip
Show source codeHide source code
fn pub mut skip -> Result[Nil, Error] {
match value_type {
case Ok(Number) -> {
try number
nil
}
case Ok(Array) -> try values(fn { skip })
case Ok(Object) -> try keys(fn (_) { skip })
case Ok(String) -> try string
case Ok(Null) -> try null
case Ok(Bool) -> try bool
case Error(e) -> throw e
}
Result.Ok(nil)
}
fn pub mut skip -> Result[Nil, Error]
Recursively parses but ignores the current value.
Examples
import std.json (PullParser)
PullParser.new('[10, 20]').skip # => Result.Ok(nil)
start_of_next_value
Show source codeHide source code
fn pub mut start_of_next_value -> Result[Int, Error] {
try skip_whitespace
Result.Ok(@offset + 1)
}
fn pub mut start_of_next_value -> Result[Int, Error]
Returns the byte offset of the start of the next non-whitespace value.
This method is useful when you want to obtain the start of some value and use it when reporting an error when parsing said value, such as when you want to parse a JSON string into a Markdown document.
Examples
import std.json (PullParser)
let parser = PullParser.new(' 10')
parser.start_of_next_value # => Result.Ok(2)
string
Show source codeHide source code
fn pub mut string -> Result[String, Error] {
match try peek {
case Some(DQUOTE) -> try advance
case Some(byte) -> throw unexpected(byte)
case _ -> throw missing_input
}
loop {
match try advance {
case Some(BSLASH) -> {
match try advance {
case Some(LOWER_U) -> try escaped_unicode
case Some(byte) -> {
match ESCAPE_TABLE.get(byte) {
case -1 -> throw unexpected(byte)
case val -> @buffer.push(val)
}
}
case _ -> throw missing_input
}
}
case Some(DQUOTE) -> break
case Some(val) if val >= 0x0 and val <= 0x001F -> {
throw generic_error(
'control characters in the range 0x0..0x001F must be escaped',
)
}
case Some(byte) -> @buffer.push(byte)
case _ -> throw missing_input
}
}
Result.Ok(@buffer.drain_to_string)
}
fn pub mut string -> Result[String, Error]
Parses a string.
Examples
import std.json (PullParser)
PullParser.new('"hello"').string # => Result.Ok('hello')
value_type
Show source codeHide source code
fn pub mut value_type -> Result[Type, Error] {
let typ = match try peek {
case Some(MINUS) -> Type.Number
case Some(BRACKET_OPEN) -> Type.Array
case Some(CURLY_OPEN) -> Type.Object
case Some(LOWER_T or LOWER_F) -> Type.Bool
case Some(LOWER_N) -> Type.Null
case Some(DQUOTE) -> Type.String
case Some(byte) if digit?(byte) -> Type.Number
# This is to take care of any random garbage that may be included in the
# JSON document, including Unicode BOMs. This also saves us from having to
# explicitly check for all the different BOMs.
case Some(byte) -> throw unexpected(byte)
case _ -> throw missing_input
}
Result.Ok(typ)
}
fn pub mut value_type -> Result[Type, Error]
Returns a Type
describing what type of value is located at the current
offset.
values
Show source codeHide source code
fn pub mut values(value: fn -> Result[Nil, Error]) -> Result[Nil, Error] {
try expect(BRACKET_OPEN, skip_whitespace: true)
loop {
match try peek {
case Some(BRACKET_CLOSE) -> {
try advance
break
}
case Some(_) -> {
try value.call
try separator(BRACKET_CLOSE)
}
case _ -> throw missing_input
}
}
Result.Ok(nil)
}
fn pub mut values(value: fn -> Result[Nil, Error]) -> Result[Nil, Error]
Parses an array, calling the value
closure for every value in the array.
It's expected that the value
closure advances the parser by parsing the
value in it's desired format.
Examples
import std.json (PullParser)
let parser = PullParser.new('[10, 20]')
let values = []
parser
.values(fn {
values.push(parser.int.get)
Result.Ok(nil)
})
.get
values # => [10, 20]