Json_Value :: struct { kind: enum { none; bool; number; string; array; object; }; using value: union { b: bool; n: float64; s: string; array: [..]Json_Value; object: struct { keys: [..]string; values: [..]Json_Value; } } } json_serialize :: (value: $T) -> Json_Value { json_serialize_with_info :: (info: *Type_Info, value: *void) -> Json_Value { if value == null { return .{ kind = .none }; } if info.type == { case .BOOL; return .{ kind = .bool, b = value.(*bool).* }; case .INTEGER; i_info := info.(*Type_Info_Integer); if i_info.signed { if i_info.runtime_size == { case 1; return .{ kind = .number, n = (value.( *s8).*).(float64) }; case 2; return .{ kind = .number, n = (value.(*s16).*).(float64) }; case 4; return .{ kind = .number, n = (value.(*s32).*).(float64) }; case 8; return .{ kind = .number, n = (value.(*s64).*).(float64) }; case; basic.assert(false, "unknown float size: %", info.runtime_size); } } else { if i_info.runtime_size == { case 1; return .{ kind = .number, n = (value.( *u8).*).(float64) }; case 2; return .{ kind = .number, n = (value.(*u16).*).(float64) }; case 4; return .{ kind = .number, n = (value.(*u32).*).(float64) }; case 8; return .{ kind = .number, n = (value.(*u64).*).(float64) }; case; basic.assert(false, "unknown float size: %", info.runtime_size); } } case .FLOAT; if info.runtime_size == { case 4; return .{ kind = .number, n = (value.(*float32).*).(float64) }; case 8; return .{ kind = .number, n = (value.(*float64).*) }; case; basic.assert(false, "unknown float size: %", info.runtime_size); } case .STRING; return .{ kind = .string, s = (value.(*string)).* }; case .STRUCT; json_obj: Json_Value; json_obj.kind = .object; obj := *json_obj.object; vptr := value.(*u8); s_info := info.(*Type_Info_Struct); for s_info.members if it.offset_in_bytes >= 0 { name := get_json_member_name(it); basic.array_add(*obj.keys, name); field_value := vptr + it.offset_in_bytes; basic.array_add(*obj.values, json_serialize_with_info(it.type, field_value)); } return json_obj; case .ARRAY; json_array: Json_Value; json_array.kind = .array; arr := *json_array.array; vptr := value.(*u8); a_info := info.(*Type_Info_Array); el_info := a_info.element_type; if a_info.array_type == { case .FIXED; for 0..a_info.array_count - 1 { el_ptr := vptr + (it * el_info.runtime_size); basic.array_add(arr, json_serialize_with_info(el_info, el_ptr)); } case .VIEW; #through; case .RESIZABLE; data := vptr.(*[]u8); for 0..data.count - 1 { el_ptr := data.data + (it * el_info.runtime_size); basic.array_add(arr, json_serialize_with_info(el_info, el_ptr)); } } return json_array; } basic.assert(false, "unhandled type in marshal: %", info.type); return .{}; } return json_serialize_with_info(T.(*Type_Info), *value); } json_deserialize :: (data: string) -> (Json_Value, bool) { p := Parser.{ data = data.([]u8), allocator = context.allocator, }; // @todo(judah): logging return parse_value(*p), p.error.count == 0; } json_deserialize :: ($T: Type, data: string) -> (T, bool) { json_deserialize_with_info :: (info: *Type_Info, json: Json_Value, ptr: *void) -> bool { if json.kind == { case .none; return true; case .bool; basic.assert(info.type == .BOOL, "cannot deserialize % into %", json.b, info.type); // @errors ptr.(*bool).* = json.b; case .number; if info.type == { case .INTEGER; i_info := info.(*Type_Info_Integer); if i_info.signed { // @todo(judah): bounds check to make sure this is safe if i_info.runtime_size == { case 1; ptr.(*s8 ).* = json.n.(s8 ); case 2; ptr.(*s16).* = json.n.(s16); case 4; ptr.(*s32).* = json.n.(s32); case 8; ptr.(*s64).* = json.n.(s64); case; basic.assert(false, "unhandled int size % in deserialize", i_info.runtime_size); } } else { if i_info.runtime_size == { case 1; ptr.(*u8 ).* = json.n.(u8 ); case 2; ptr.(*u16).* = json.n.(u16); case 4; ptr.(*u32).* = json.n.(u32); case 8; ptr.(*u64).* = json.n.(u64); case; basic.assert(false, "unhandled uint size % in deserialize", i_info.runtime_size); } } case .FLOAT; if info.runtime_size == { case 4; ptr.(*float32).* = json.n.(float32); case 8; ptr.(*float64).* = json.n.(float64); case; basic.assert(false, "unhandled float size % in deserialize", info.runtime_size); } } return true; case .string; basic.assert(info.type == .STRING, "cannot deserialize '%' into %", json.s, info.type); // @errors sptr := ptr.(*string); sptr.data = basic.copy_string(json.s).data; sptr.count = json.s.count; return true; case .object; basic.assert(info.type == .STRUCT, "cannot deserialize json object into %", info.type); // @errors vptr := ptr.(*u8); s_info := info.(*Type_Info_Struct); for mem: s_info.members { found := false; name := get_json_member_name(mem); value: Json_Value; for json.object.keys if it == name { // @todo(judah): handle custom names in notes found = true; value = json.object.values[it_index]; break; } if !found continue; field_ptr := vptr + mem.offset_in_bytes; if !json_deserialize_with_info(mem.type, value, field_ptr) { return false; } } return true; case .array; basic.assert(info.type == .ARRAY, "cannot deserialize json array into %", info.type); // @errors vptr := ptr.(*u8); a_info := info.(*Type_Info_Array); el_info := a_info.element_type; if a_info.array_type == { case .FIXED; basic.assert(json.array.count <= a_info.array_count); for 0..a_info.array_count - 1 { el_ptr := vptr + (it * el_info.runtime_size); value := json.array[it]; if !json_deserialize_with_info(el_info, value, el_ptr) { return false; } } case .VIEW; #through; case .RESIZABLE; data := basic.alloc(json.array.count * el_info.runtime_size).(*u8); if json.array.count == 0 { return true; } // @todo(judah): verify the json array is homogenous for json.array { el_ptr := data + (it_index * el_info.runtime_size); if !json_deserialize_with_info(el_info, it, el_ptr) { return false; } } if a_info.array_type == .VIEW { out := ptr.(*[]u8); out.data = data; out.count = json.array.count; } else { out := ptr.(*[..]u8); out.data = data; out.count = json.array.count; out.allocated = json.array.count; } return true; } return true; } basic.assert(false, "unhandled json value: %", json.kind); return false; } json, ok := json_deserialize(data); if !ok return zero_of(T), ok; out: T; ok = json_deserialize_with_info(T.(*Type_Info), json, *out); return out, ok; } to_string :: (value: Json_Value, indent := 0) -> string { b: basic.String_Builder; add_indents :: (b: *basic.String_Builder, level: int) #expand { if level != 0 { for 0..level basic.append(b, " "); } } if value.kind == { case .none; basic.append(*b, "null"); case .bool; if value.b { basic.append(*b, "true"); } else { basic.append(*b, "false"); } case .number; basic.print_to_builder(*b, "%", value.n); case .string; basic.print_to_builder(*b, "\"%\"", value.s); case .array; basic.append(*b, "[\n"); for value.array { add_indents(*b, indent + 2); basic.append(*b, to_string(it, indent + 2)); if it_index < value.array.count { basic.append(*b, ",\n"); } } add_indents(*b, indent); basic.append(*b, "]"); case .object; basic.append(*b, "{\n"); for value.object.keys { add_indents(*b, indent + 2); v := value.object.values[it_index]; basic.print_to_builder(*b, "\"%\": %,\n", it, to_string(v, indent + 2)); } add_indents(*b, indent); basic.append(*b, "}"); } return basic.builder_to_string(*b); } #scope_file; get_json_member_name :: (member: Type_Info_Struct_Member) -> string { name := member.name; for member.notes if strings.contains(it, "json:") { name = .{ data = it.data + 5, count = it.count - 5 }; name = strings.replace(name, "\\_", " "); break; } return name; } Parser :: struct { allocator: Allocator; data: []u8; error: string; offset: u64; line: u64; } Token :: struct { kind: enum { invalid; eof; true_; false_; null_; number; str; colon :: #char ":"; comma :: #char ","; l_brace :: #char "{"; r_brace :: #char "}"; l_square :: #char "["; r_square :: #char "]"; }; str: string; } get_next_token :: (p: *Parser) -> Token { skip_whitespace(p); if at_end(p) { return .{ kind = .eof }; } c := p.data[p.offset]; if c == { case #char ":"; #through; case #char ","; #through; case #char "{"; #through; case #char "}"; #through; case #char "["; #through; case #char "]"; p.offset += 1; return .{ kind = xx c }; } // Comments if c == #char "/" && p.offset + 1 < p.data.count.(u64) && p.data[p.offset+1] == #char "/" { capture_while(p, c => c != #char "\n"); return get_next_token(p); } // Strings // @todo(judah): handle escaped quotes if c == #char "\"" { p.offset += 1; // skip " str := capture_while(p, s => s != #char "\""); p.offset += 1; // skip " return .{ kind = .str, str = str }; } // Numbers if (c >= #char "0" && c <= #char "9") || c == #char "." { number := capture_while(p, n => (n >= #char "0" && n <= #char "9") || n == #char "."); return .{ kind = .number, str = number }; } // true, false, null if (c >= #char "a" && c <= #char "z") { keyword := capture_while(p, n => (n >= #char "a" && n <= #char "z")); if keyword == { case "true"; return .{ kind = .true_ }; case "false"; return .{ kind = .false_ }; case "null"; return .{ kind = .null_ }; case; return .{ kind = .invalid, str = keyword }; } } return .{ kind = .eof }; } peek_token :: (p: *Parser) -> Token { copy := p.*; // @todo(judah): this is bad? return get_next_token(*copy); } expect :: (p: *Parser, kinds: ..type_of(Token.kind)) -> (bool, Token) { tok := get_next_token(p); for kinds if it == tok.kind { return true, tok; } return false, tok; } parse_value :: (p: *Parser) -> Json_Value { to_string :: (c: u8) -> string #expand { str: string; str.data = *c; str.count = 1; return str; } ok, tok := expect(p, .l_brace, .l_square, .str, .number, .true_, .false_, .null_); if !ok { set_error(p, "expected value, found '%'", tok.str); return .{}; } if tok.kind == { case .number; number := tok.str; // @todo(judah): parse_float works a bit weird f, ok := strings.parse_float(*number); if !ok { set_error(p, "malformed number '%'", tok.str); return .{}; } return .{ kind = .number, n = f }; case .true_; #through; case .false_; return .{ kind = .bool, b = tok.kind == .true_ }; case .null_; return .{ kind = .none }; case .str; return .{ kind = .string, s = tok.str }; case .l_brace; object: Json_Value; object.kind = .object; obj := *object.object; obj.keys.allocator = p.allocator; obj.values.allocator = p.allocator; while !at_end(p) { end := peek_token(p); if end.kind == .r_brace break; key_ok, key := expect(p, .str); if !key_ok { set_error(p, "expected key for object"); return .{}; } if !expect(p, .colon) { set_error(p, "missing ':' after key"); return .{}; } value := parse_value(p); if p.error.count != 0 { return .{}; } basic.array_add(*obj.keys, key.str); basic.array_add(*obj.values, value); end = peek_token(p); if end.kind == .r_brace break; if !expect(p, .comma) { set_error(p, "missing ',' after value"); return .{}; } } if !expect(p, .r_brace) { set_error(p, "missing '}' at end of object"); return .{}; } return object; case .l_square; array: Json_Value; array.kind = .array; array.array.allocator = p.allocator; while !at_end(p) { end := peek_token(p); if end.kind == .r_square break; value := parse_value(p); if p.error.count != 0 { return .{}; } basic.array_add(*array.array, value); end = peek_token(p); if end.kind == .r_square break; if !expect(p, .comma) { set_error(p, "missing ',' after value"); return .{}; } } if !expect(p, .r_square) { set_error(p, "missing ']' at end of array"); return .{}; } return array; } return .{}; } set_error :: (p: *Parser, message: string, args: ..Any) { p.error = basic.tprint("line %, %", p.line, basic.tprint(message, ..args)); } skip_whitespace :: (p: *Parser) { while !at_end(p) { c := p.data[p.offset]; if c == { case #char " "; #through; case #char "\t"; #through; case #char "\r"; p.offset += 1; case #char "\n"; p.offset += 1; p.line += 1; case; break; } } } capture_while :: (p: *Parser, proc: (u8) -> bool) -> string { basic.assert(p.offset < p.data.count.(u64)); capture := p.data; capture.data += p.offset; capture.count = 0; while !at_end(p) { c := p.data[p.offset]; if !proc(c) break; p.offset += 1; } capture.count = ((p.data.data + p.offset) - capture.data).(s64); return capture.(string); } at_end :: inline (p: *Parser) -> bool { return p.offset >= p.data.count.(u64); } basic :: #import "Basic"; // @future strings :: #import "String"; // @future