Skip to content

Instantly share code, notes, and snippets.

@mzaks
Last active June 5, 2024 09:56
Show Gist options
  • Save mzaks/c1947212fd23ba010129d2f6b7c0fdfa to your computer and use it in GitHub Desktop.
Save mzaks/c1947212fd23ba010129d2f6b7c0fdfa to your computer and use it in GitHub Desktop.
Quick and Dirty JSON parser in Mojo
@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