json_parser

A complete JSON parser for MoonBit with full spec compliance, error handling, and pretty-printing support

json
parser
parsing
serialization
deserialization
moon add tonyfettes/json_parser@0.1.0
Download zip
Version
0.1.0
License
Apache-2.0
Last updated
9 months ago
Downloads
17
README

#JSON Parser

A complete JSON parser implementation in MoonBit with full support for all JSON types.

#Features

  • ✅ Parse all JSON types: null, boolean, number, string, array, object
  • ✅ String escape sequences including Unicode escapes (\uXXXX)
  • ✅ Scientific notation for numbers
  • ✅ Detailed error messages with position information
  • ✅ Pretty-print JSON with indentation
  • ✅ Comprehensive error handling using checked errors

#Usage

#Parsing JSON

///|
test "basic parsing example" {
// Parse a JSON string
let json_str = "{\"name\": \"Alice\", \"age\": 30, \"active\": true}"
let result = try! @tonyfettes/json_parser.parse(json_str)
match result {
Ok(Object(obj)) => {
inspect(obj.get("name"), content="Some(String(\"Alice\"))")
inspect(obj.get("age"), content="Some(Number(30))")
inspect(obj.get("active"), content="Some(Bool(true))")
}
Err(err) => println("Parse error: \{err}")
_ => ()
}
}

#Working with JSON Values

///|
test "construct and access JSON" {
// Create a JSON object manually
let person : Map[String, @tonyfettes/json_parser.JsonValue] = {
"name": String("Bob"),
"age": Number(25),
"hobbies": Array([String("reading"), String("coding")]),
}
let json = @tonyfettes/json_parser.JsonValue::Object(person)

// Convert to string and parse it back to verify
let json_string = json.to_json_string(0)
let reparsed = try! @tonyfettes/json_parser.parse(json_string)
match reparsed {
Ok(Object(obj)) => {
inspect(obj.get("name"), content="Some(String(\"Bob\"))")
inspect(obj.get("age"), content="Some(Number(25))")
}
_ => ()
}
}

#Parsing Arrays

///|
test "parse JSON arrays" {
let json_str = "[1, 2, 3, 4, 5]"
let result = try! @tonyfettes/json_parser.parse(json_str)
match result {
Ok(Array(arr)) => {
inspect(arr.length(), content="5")
inspect(arr[0], content="Number(1)")
inspect(arr[4], content="Number(5)")
}
_ => fail("Expected array")
}
}

#Nested Structures

///|
test "parse nested JSON" {
let json_str =
#|{
#| "users": [
#| {"id": 1, "name": "Alice"},
#| {"id": 2, "name": "Bob"}
#| ],
#| "total": 2
#|}
let result = try! @tonyfettes/json_parser.parse(json_str)
match result {
Ok(Object(root)) => {
// Access nested values
match root.get("users") {
Some(Array(users)) => {
inspect(users.length(), content="2")

// Access first user
match users[0] {
Object(user) =>
inspect(user.get("name"), content="Some(String(\"Alice\"))")
_ => ()
}
}
_ => ()
}
inspect(root.get("total"), content="Some(Number(2))")
}
_ => fail("Expected object")
}
}

#Error Handling

///|
test "handle parse errors" {
// Invalid JSON - unexpected character
let invalid1 = try! @tonyfettes/json_parser.parse("@invalid")
match invalid1 {
Err(UnexpectedChar(_pos, _ch)) => ()
_ => fail("Expected UnexpectedChar error")
}

// Unclosed string
let invalid2 = try! @tonyfettes/json_parser.parse("\"unclosed")
match invalid2 {
Err(UnexpectedEnd) => ()
_ => fail("Expected UnexpectedEnd error")
}

// Invalid number
let invalid3 = try! @tonyfettes/json_parser.parse("123.456.789")
match invalid3 {
Err(_) => () // Any error is acceptable
Ok(_) => fail("Should have failed to parse")
}
}

#String Escapes

///|
test "parse strings with escapes" {
let json_with_escapes = "\"Line 1\\nLine 2\\tTabbed\\u0041BC\""
let result = try! @tonyfettes/json_parser.parse(json_with_escapes)
match result {
Ok(String(s)) => {
// Contains newline, tab, and Unicode escape for 'A'
assert_true(s.contains("\n"))
assert_true(s.contains("\t"))
assert_true(s.contains("ABC"))
}
_ => fail("Expected string")
}
}

#Pretty Printing

///|
test "pretty print JSON" {
let data : Map[String, @tonyfettes/json_parser.JsonValue] = {
"name": String("Charlie"),
"scores": Array([Number(95), Number(87), Number(92)]),
}
let json = @tonyfettes/json_parser.JsonValue::Object(data)
let formatted = json.to_json_string(0)

// The output will be nicely formatted with indentation
// Verify by parsing it back
let reparsed = try! @tonyfettes/json_parser.parse(formatted)
match reparsed {
Ok(Object(obj)) =>
inspect(obj.get("name"), content="Some(String(\"Charlie\"))")
_ => ()
}
}

#API Reference

#Types

#JsonValue

The main type representing JSON values:

///|
enum JsonValue {
Null
Bool(Bool)
Number(Double)
String(String)
Array(Array[JsonValue])
Object(Map[String, JsonValue])
}

#ParseError

Error types that can occur during parsing:

///|
suberror ParseError {
UnexpectedChar(Position, Char) // Unexpected character at position
UnexpectedEnd // Unexpected end of input
InvalidNumber(Position, String) // Invalid number format
InvalidEscape(Position, Char) // Invalid escape sequence
InvalidUnicode(Position, String) // Invalid Unicode escape
Expected(Position, String) // Expected a specific token
}

#Functions

#parse(input : String) -> JsonValue raise ParseError

Parse a JSON string into a JsonValue.

Parameters:
  • input: The JSON string to parse

Returns: The parsed JsonValue

Raises: ParseError if the input is not valid JSON

#JsonValue::to_json_string(self : JsonValue, indent : Int) -> String

Convert a JsonValue to a formatted JSON string.

Parameters:
  • self: The JSON value to convert
  • indent: The indentation level (use 0 for root level)

Returns: A formatted JSON string

#Implementation Details

The parser is implemented in three main components:

  1. Lexer: Tokenizes the input string into JSON tokens
  2. Parser: Builds the JSON value tree from tokens
  3. Formatter: Converts JSON values back to strings

The implementation uses:
  • Checked error handling with MoonBit's raise system
  • Pattern matching for robust parsing logic
  • Efficient string building with Buffer
  • Position tracking for helpful error messages

#Testing

Run the test suite:

moon test -p tonyfettes/json_parser

Update test snapshots after changes:

moon test -p tonyfettes/json_parser --update

#License

See LICENSE file for details.

#
ParseError

pub(all) suberror ParseError {
UnexpectedChar(Position, Char)
UnexpectedEnd
InvalidNumber(Position, String)
InvalidEscape(Position, Char)
InvalidUnicode(Position, String)
Expected(Position, String)
}

Parser errors
impl Eq for ParseError
impl Show for ParseError

#
JsonValue

pub(all) enum JsonValue {
Null
Bool(Bool)
Number(Double)
String(String)
Array(Array[JsonValue])
Object(Map[String, JsonValue])
}

JSON value types
impl Eq for JsonValue
impl Show for JsonValue

#
JsonValue::to_json_string

fn JsonValue::to_json_string(self : JsonValue, indent : Int) -> String

Convert JsonValue to a formatted string

#
Position

type Position

Position in the input for error reporting
impl Eq for Position
impl Show for Position

#
parse

fn parse(input : String) -> JsonValue raise ParseError

Parse JSON from a string

Source Files