Last active
June 5, 2024 09:56
-
-
Save mzaks/c1947212fd23ba010129d2f6b7c0fdfa to your computer and use it in GitHub Desktop.
Quick and Dirty JSON parser in Mojo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@value | |
struct JSONValue(CollectionElement): | |
var _text: String | |
fn __init__(inout self, text: String): | |
self._text = text.strip() | |
fn is_object(self) -> Bool: | |
return self._text.startswith("{") and self._text.endswith("}") | |
fn is_array(self) -> Bool: | |
return self._text.startswith("[") and self._text.endswith("]") | |
fn is_string(self) -> Bool: | |
return self._text.startswith("\"") and self._text.endswith("\"") | |
fn is_bool(self) -> Bool: | |
return self._text == "true" or self._text == "false" | |
fn is_null(self) -> Bool: | |
return self._text == "null" | |
fn is_number(self) -> Bool: | |
var first_char = self._text[0] | |
return first_char == "-" or first_char in first_char.DIGITS | |
fn get_string(self) raises -> String: | |
if not self.is_string(): | |
raise "Value is not a string" | |
return self._text[1:-1] | |
fn get_bool(self) raises -> Bool: | |
if self._text == "true": | |
return True | |
elif self._text == "false": | |
return False | |
raise "Value is not bool" | |
fn get_int(self) raises -> Int: | |
if not self.is_number(): | |
raise "Value is not a number" | |
if "." in self._text: | |
raise "Value is not an int" | |
return int(self._text) | |
fn get_float(self) raises -> Float64: | |
if not self.is_number(): | |
raise "Value is not a number" | |
var parts = self._text.split(".") | |
var parts_count = len(parts) | |
if parts_count == 0 or parts_count > 2: | |
raise "Value is not a floating point number" | |
var num1 = Float64(int(parts[0])) | |
if parts_count == 1: | |
return num1 | |
var num2 = Float64(int(parts[1])) / (10 ** len(parts[1])) | |
return num1 + num2 * (-1 if self._text.startswith("-") else 1) | |
fn get_array(self) raises -> List[JSONValue]: | |
var array = List[JSONValue]() | |
var chars = self._text[1:-1].as_bytes() | |
var value = String("") | |
alias COMMA = UInt8(ord(",")) | |
alias OPENA = UInt8(ord("[")) | |
alias CLOSEA = UInt8(ord("]")) | |
alias OPENO = UInt8(ord("{")) | |
alias CLOSEO = UInt8(ord("}")) | |
alias QUOTE = UInt8(ord("\"")) | |
alias ESCAPE = UInt8(ord("\\")) | |
var index = 0 | |
while index < (len(chars)): | |
var c = chars[index] | |
if c == COMMA: | |
array.append(value) | |
value = "" | |
elif c == QUOTE: | |
value += chr(int(c)) | |
index += 1 | |
var cc = chars[index] | |
while cc != QUOTE or (cc == QUOTE and chars[index - 1] == ESCAPE): | |
value += chr(int(cc)) | |
index += 1 | |
cc = chars[index] | |
value += chr(int(c)) | |
elif c == OPENA: | |
var open = 1 | |
var in_quotes = False | |
while open > 0: | |
value += chr(int(c)) | |
index += 1 | |
c = chars[index] | |
if c == QUOTE and chars[index - 1] != ESCAPE: | |
in_quotes = not in_quotes | |
elif c == OPENA and not in_quotes: | |
open += 1 | |
elif c == CLOSEA and not in_quotes: | |
open -= 1 | |
value += chr(int(c)) | |
elif c == OPENO: | |
var open = 1 | |
var in_quotes = False | |
while open > 0: | |
value += chr(int(c)) | |
index += 1 | |
c = chars[index] | |
if c == QUOTE and chars[index - 1] != ESCAPE: | |
in_quotes = not in_quotes | |
elif c == OPENO and not in_quotes: | |
open += 1 | |
elif c == CLOSEO and not in_quotes: | |
open -= 1 | |
value += chr(int(c)) | |
else: | |
value += chr(int(c)) | |
index += 1 | |
if len(value.strip()) > 0: | |
array.append(value) | |
return array | |
fn get_object(self) raises -> Dict[String, JSONValue]: | |
var result = Dict[String, JSONValue]() | |
var chars = self._text[1:-1].as_bytes() | |
var key = String("") | |
var value = String("") | |
alias COMMA = UInt8(ord(",")) | |
alias OPENA = UInt8(ord("[")) | |
alias CLOSEA = UInt8(ord("]")) | |
alias OPENO = UInt8(ord("{")) | |
alias CLOSEO = UInt8(ord("}")) | |
alias QUOTE = UInt8(ord("\"")) | |
alias ESCAPE = UInt8(ord("\\")) | |
alias COLUMN = UInt8(ord(":")) | |
var index = 0 | |
var scan_for_key = True | |
var scan_for_column = False | |
var consume_value = False | |
while index < (len(chars)): | |
var c = chars[index] | |
if scan_for_key: | |
if c == QUOTE: | |
key = "" | |
index += 1 | |
scan_for_key = False | |
var cc = chars[index] | |
while cc != QUOTE or (cc == QUOTE and chars[index - 1] == ESCAPE): | |
key += chr(int(cc)) | |
index += 1 | |
cc = chars[index] | |
scan_for_column = True | |
elif scan_for_column and c == COLUMN: | |
scan_for_column = False | |
consume_value = True | |
elif consume_value: | |
if c == COMMA: | |
result[key] = value | |
value = "" | |
consume_value = False | |
scan_for_key = True | |
elif c == QUOTE: | |
value += chr(int(c)) | |
index += 1 | |
var cc = chars[index] | |
while cc != QUOTE or (cc == QUOTE and chars[index - 1] == ESCAPE): | |
value += chr(int(cc)) | |
index += 1 | |
cc = chars[index] | |
value += chr(int(c)) | |
elif c == OPENA: | |
var open = 1 | |
var in_quotes = False | |
while open > 0: | |
value += chr(int(c)) | |
index += 1 | |
c = chars[index] | |
if c == QUOTE and chars[index - 1] != ESCAPE: | |
in_quotes = not in_quotes | |
elif c == OPENA and not in_quotes: | |
open += 1 | |
elif c == CLOSEA and not in_quotes: | |
open -= 1 | |
value += chr(int(c)) | |
elif c == OPENO: | |
var open = 1 | |
var in_quotes = False | |
while open > 0: | |
value += chr(int(c)) | |
index += 1 | |
c = chars[index] | |
if c == QUOTE and chars[index - 1] != ESCAPE: | |
in_quotes = not in_quotes | |
elif c == OPENO and not in_quotes: | |
open += 1 | |
elif c == CLOSEO and not in_quotes: | |
open -= 1 | |
value += chr(int(c)) | |
else: | |
value += chr(int(c)) | |
index += 1 | |
if len(key) + len(value) > 0: | |
result[key] = value | |
return result | |
from testing import assert_equal | |
from time import now | |
def main(): | |
v1 = JSONValue("\"hello\"") | |
assert_equal(v1.get_string(), "hello") | |
v1 = JSONValue(" \"hello\" ") | |
assert_equal(v1.get_string(), "hello") | |
v1 = JSONValue(" \"hello 🔥 this is fun\" ") | |
assert_equal(v1.get_string(), "hello 🔥 this is fun") | |
v1 = JSONValue(" true ") | |
assert_equal(v1.get_bool(), True) | |
v1 = JSONValue(" false ") | |
assert_equal(v1.get_bool(), False) | |
v1 = JSONValue("null ") | |
assert_equal(v1.is_null(), True) | |
v1 = JSONValue(" 45") | |
assert_equal(v1.get_int(), 45) | |
assert_equal(v1.get_float(), 45) | |
v1 = JSONValue(" -45") | |
assert_equal(v1.get_int(), -45) | |
assert_equal(v1.get_float(), -45) | |
v1 = JSONValue("1.2") | |
assert_equal(v1.get_float(), 1.2) | |
v1 = JSONValue("-1.25") | |
assert_equal(v1.get_float(), -1.25) | |
v1 = JSONValue("-1345.1001") | |
assert_equal(v1.get_float(), -1345.1001) | |
v1 = JSONValue("[]") | |
a1 = v1.get_array() | |
assert_equal(len(a1), 0) | |
v1 = JSONValue("[ ]") | |
a1 = v1.get_array() | |
assert_equal(len(a1), 0) | |
v1 = JSONValue("[1, 2, 3]") | |
a1 = v1.get_array() | |
assert_equal(len(a1), 3) | |
assert_equal(a1[0].get_int(), 1) | |
assert_equal(a1[1].get_int(), 2) | |
assert_equal(a1[2].get_int(), 3) | |
v1 = JSONValue("[1, \"2, 3\"]") | |
a1 = v1.get_array() | |
assert_equal(len(a1), 2) | |
assert_equal(a1[0].get_int(), 1) | |
assert_equal(a1[1].get_string(), "2, 3") | |
v1 = JSONValue("[1, \"\\\"2, \\\"3\"]") | |
a1 = v1.get_array() | |
assert_equal(len(a1), 2) | |
assert_equal(a1[0].get_int(), 1) | |
assert_equal(a1[1].get_string(), "\\\"2, \\\"3") | |
v1 = JSONValue("[1, [2, 3]]") | |
a1 = v1.get_array() | |
assert_equal(len(a1), 2) | |
assert_equal(a1[0].get_int(), 1) | |
a2 = a1[1].get_array() | |
assert_equal(len(a2), 2) | |
assert_equal(a2[0].get_int(), 2) | |
assert_equal(a2[1].get_int(), 3) | |
v1 = JSONValue("[1, { \"a\": 2, \"b\": 3}, 4]") | |
a1 = v1.get_array() | |
assert_equal(len(a1), 3) | |
assert_equal(a1[0].get_int(), 1) | |
assert_equal(a1[2].get_int(), 4) | |
o1 = a1[1].get_object() | |
assert_equal(len(o1), 2) | |
assert_equal(o1["a"].get_int(), 2) | |
assert_equal(o1["b"].get_int(), 3) | |
v1 = JSONValue("{}") | |
o1 = v1.get_object() | |
assert_equal(len(o1), 0) | |
v1 = JSONValue("{ }") | |
o1 = v1.get_object() | |
assert_equal(len(o1), 0) | |
v1 = JSONValue("{ \"a\": [1, \"]\", 3] }") | |
o1 = v1.get_object() | |
assert_equal(len(o1), 1) | |
a1 = o1["a"].get_array() | |
assert_equal(len(a1), 3) | |
assert_equal(a1[0].get_int(), 1) | |
assert_equal(a1[1].get_string(), "]") | |
assert_equal(a1[2].get_int(), 3) | |
v1 = JSONValue("[ [1, \"]\", 3] ]") | |
a1 = v1.get_array() | |
assert_equal(len(a1), 1) | |
a2 = a1[0].get_array() | |
assert_equal(len(a2), 3) | |
assert_equal(a2[0].get_int(), 1) | |
assert_equal(a2[1].get_string(), "]") | |
assert_equal(a2[2].get_int(), 3) | |
v1 = JSONValue("""[ {"a": 1, "b": "}", "c": 3} ]""") | |
a1 = v1.get_array() | |
assert_equal(len(a1), 1) | |
o1 = a1[0].get_object() | |
assert_equal(len(o1), 3) | |
assert_equal(o1["a"].get_int(), 1) | |
assert_equal(o1["b"].get_string(), "}") | |
assert_equal(o1["c"].get_int(), 3) | |
v1 = JSONValue("""{"first": {"a": 1, "b": "}", "c": 3}, "second" : true }""") | |
o1 = v1.get_object() | |
assert_equal(len(o1), 2) | |
assert_equal(o1["second"].get_bool(), True) | |
o2 = o1["first"].get_object() | |
assert_equal(len(o2), 3) | |
assert_equal(o2["a"].get_int(), 1) | |
assert_equal(o2["b"].get_string(), "}") | |
assert_equal(o2["c"].get_int(), 3) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment