Search results

There are no results.

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 code
Hide 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 code
Hide 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 code
Hide 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 code
Hide 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 code
Hide 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 code
Hide 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 code
Hide 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 code
Hide 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 code
Hide source code
fn pub offset -> Int {
  @offset
}
fn pub offset -> Int

Returns the current byte offset in the input stream.

skip

Show source code
Hide 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 code
Hide 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 code
Hide 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 code
Hide 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 code
Hide 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]