#import, file "../module.jai"; main :: () { { // Use the temporary allocator so we don't have to worry about individually freeing // all the allocations. push_allocator(temp); typed_parsing(); typed_printing(); generic_parsing(); generic_printing(); rename_by_note(); custom_rename_procedure(); decode_into_struct_with_using(); enum_errors(); } // Since the program ends here, this doesn't matter, but just setting an example. reset_temporary_storage(); } // Data structures for typed parsing/printing LevelData :: struct { kind: LevelKind; flags: LevelFlags; secret: bool; player: Entity; player2: *Entity; union { score: float; score_v2: float; } entities: [..] Entity; floats: [] float; LevelKind :: enum { EASY :: 0; HARD :: 1; LEGENDARY :: 2; } LevelFlags :: enum_flags { FLAG_A :: 0x1; FLAG_B :: 0x2; FLAG_C :: 0x4; } } Entity :: struct { name: string; x, y: int; dirty: bool; @JsonIgnore } LEVEL_DATA_1_JSON := #string DONE {"kind": ".HARD", "flags": "FLAG_A | LevelFlags.FLAG_C", "secret": false,"score":5.5,"player": {"name": "Pat","x": 10,"y": 10},"player2": {"name": "Chris"},"entities": [{"name": "fdsa","x": 0,"y": 0},{"name": "fdsa","x": 0,"y": 0}], "floats": [0.00, 1.11111111111111111, 2.0202, 3e-5, 4.444444, -5.0]} DONE; LEVEL_DATA_2_JSON := #string DONE {"kind": 2, "score_v2": 25.1} DONE; Fixed_Size_Data :: struct { data: [3] int; } DATA_ARRAY_1 := #string DONE {"data": [1, 2, 3]} DONE; DATA_ARRAY_2 := #string DONE {"data": [1, 2]} DONE; typed_parsing :: () { { success, level := json_parse_string(LEVEL_DATA_1_JSON, LevelData, ignore_unknown=false); // success, level := json_parse_file("level.json", LevelData, ignore_unknown=false); assert(success); log("Typed parsing result 1:\n%\nscore: %\n\n", level, level.score); assert(level.floats.count == 6); assert(level.floats[1] > 1); // Regression test, we had a bug here… } { // Test parsing integers into enum slots & alternative union fields success, level := json_parse_string(LEVEL_DATA_2_JSON, LevelData, ignore_unknown=false); assert(success); log("Typed parsing result 2:\n%\nscore: %\n\n", level, level.score); assert(level.kind == .LEGENDARY); assert(level.score == 25.1); } { // Test parsing into fixed-sized arrays success, result := json_parse_string(DATA_ARRAY_1, Fixed_Size_Data, ignore_unknown=false); assert(success); log("Typed parsing result 3:\n%\n\n", result); assert(result.data[0] == 1); assert(result.data[1] == 2); assert(result.data[2] == 3); } { // Test parsing into fixed-sized arrays (incorrect size) success, result := json_parse_string(DATA_ARRAY_2, Fixed_Size_Data, ignore_unknown=false); assert(!success); } } typed_printing :: () { level := LevelData.{kind=.LEGENDARY, flags=LevelData.LevelFlags.FLAG_B|.FLAG_C, secret=true, score=500}; level.player = .{name="Pat", x=4, y=4, dirty=true}; array_add(*level.entities, .{name="Chris", x=6, y=6}); json_string := json_write_string(level); log("Typed printing result:\n%\n\n", json_string); // success := json_write_file("level.json", level, indent_char=""); // assert(success); } generic_parsing :: () { // In this scenario, some parts of the structure are known, but other parts are not. json := #string DONE { "version": 3, "entities": [ { "name": "Player", "x": 2, "y": 2, "player_index": 0 }, { "name": "Snake", "x": 4, "y": 4, "snake_color": 1 } ], "stuff": [null, true, false] } DONE success, root := json_parse_string(json); // success, root := json_parse_file("level.json"); assert(success); log("Generic parsing result:"); // Print things out, for demonstration purposes traverse_node :: (node: JSON_Value, depth: int) { INDENTATION :: 4; print("% ", node.type); if node.type == { case .NULL; print("\n"); case .BOOLEAN; print("%\n", node.boolean); case .NUMBER; print("%\n", node.number); case .STRING; print("%\n", node.str); case .OBJECT; print("{\n"); for node.object { for 1..(depth+1)*INDENTATION print(" "); print("%: ", it_index); traverse_node(it, depth+1); } for 1..depth*INDENTATION print(" "); print("}\n"); case .ARRAY; print("[\n"); for node.array { for 1..(depth+1)*INDENTATION print(" "); traverse_node(it, depth + 1); } for 1..depth*INDENTATION print(" "); print("]\n"); } } traverse_node(root, 0); print("\n"); // Convenience function for grabbing object members get :: (json_val: JSON_Value, key: string, expected_type: JSON_Type) -> JSON_Value { assert(json_val.type == .OBJECT); table := json_val.object; success, val := Hash_Table.table_find_new(table, key); assert(success); assert(val.type == expected_type); return val; } // Check for version number that may or may not exist version: float64 = -1; assert(root.type == .OBJECT); success2, val := Hash_Table.table_find_new(root.object, "version"); if success2 { if val.type == .NUMBER { version = val.number; } } log("version: %\n", version); // Traverse a structure we are confident about for get(root, "entities", .ARRAY).array { entity_name := get(it, "name", .STRING).str; x := get(it, "x", .NUMBER).number / 32; y := get(it, "y", .NUMBER).number / 32; if entity_name == { case "Player"; player_index := cast(int) get(it, "player_index", .NUMBER).number; log("Player with player_index=%\n", player_index); case "Snake"; snake_color := cast(int) get(it, "snake_color", .NUMBER).number; log("Snake with snake_color=%\n", snake_color); case; //... } } log("\n"); } generic_printing :: () { // We want to write JSON with arbitrary structure. // Create and initialize object root_obj: JSON_Object; root := json_value(*root_obj); // Add music index to object, in certain cases should_add_music := true; if should_add_music { json_set(*root_obj, "music_index", .{type=.NUMBER, number=3}); } // Create an array of values temp: [..] JSON_Value; junk := JSON_Value.{type=.STRING, str="junk"}; array_add(*temp, junk); array_add(*temp, junk); // Create json array value array := JSON_Value.{type=.ARRAY}; array.array = temp; // Add array to object json_set(*root_obj, "junk_array", array); // Print result json_string := json_write_string(root); log("Generic_printing result:\n%\n\n", json_string); //json_write_file("level.json", root); } rename_by_note :: () { // Sometimes the JSON we are parsing contains members with names we cannot use or don´t wanna. Message :: struct { value: string; _context: struct { // We cannot use "context" because it´s a reserved keyword in Jai. channel: int; parent: int; } @JsonName(context) // This member in JSON is "context" but we need to have it as "_context". So we can use the JsonName note to encode and decode it as "context". } json := #string DONE { "value": "Hello!", "context": { "channel": 1, "parent": 897820 } } DONE success, message_decoded := json_parse_string(json, Message, ignore_unknown = false); assert(success, "Could not decode message!"); log("Rename_by_note decode result:\n%\n\n", message_decoded); assert(message_decoded._context.channel == 1); message_encoded := json_write_string(message_decoded); log("Rename_by_note encode result:\n%\n\n", message_encoded); assert(message_encoded.count != 0); assert(find_index_from_left(message_encoded, "_context") == -1); } custom_rename_procedure :: () { // We can also pass a custom rename procedure for renaming certain members. rename_to_upper :: (member: *Type_Info_Struct_Member) -> string { return to_upper_copy(member.name,,temp); } player := Entity.{ name="Player", x = 10, y = 50 }; player_encoded := json_write_string(player, rename=rename_to_upper); assert(player_encoded.count != 0); log("encoded with rename_to_upper:\n%\n\n", player_encoded); assert(find_index_from_left(player_encoded, "name") == -1); assert(find_index_from_left(player_encoded, "NAME") != -1); success, player_decoded := json_parse_string(player_encoded, Entity, ignore_unknown = false, rename = rename_to_upper); assert(success); log("decoded with rename_to_upper:\n%\n\n", player_decoded); assert(player_decoded.name == "Player"); } decode_into_struct_with_using :: () { Coordinates :: struct { x: int; y: int; } Tile :: struct { color: string; using coordinates: Coordinates; } JSON :: #string END { "color": "green", "x": 1, "y": 3 } END success, tile := json_parse_string(JSON, Tile, ignore_unknown = false); assert(success); log("Decoded tile: %", tile); assert(tile.color == "green"); assert(tile.x == 1); assert(tile.y == 3); } enum_errors :: () { // Testing error messages when parsing invalid enum values { LEVEL_DATA_BROKEN_JSON := #string DONE {"kind": ".VERY_HARD"} DONE; success, level := json_parse_string(LEVEL_DATA_BROKEN_JSON, LevelData, ignore_unknown=false); assert(!success); } { LEVEL_DATA_BROKEN_JSON := #string DONE {"flags": "FLAG_A | FLAG_D"} DONE; success, level := json_parse_string(LEVEL_DATA_BROKEN_JSON, LevelData, ignore_unknown=false); assert(!success); } } #import "Basic"; #import "Hash_Table"; // To be able to iterate over node.object #import "String"; // For to_upper_copy #import "Compiler"; // For Type_Info_Struct_Member Hash_Table :: #import "Hash_Table";