// Parse a JSON string into the given Type. // All members of Type that are not present in the JSON are kept at their default values. // All fields in the JSON that have no corresponding member in Type are ignored by default // but you can pass ignore_unknown = false to fail instead. json_parse_string :: (content: string, $T: Type, ignore_unknown := true, rename := rename_by_note) -> success: bool, T { result: T; if !content then return false, result; info := type_info(T); remainder, success := parse_value(content, cast(*u8)*result, info, ignore_unknown, "", rename=rename); if !success return false, result; remainder = trim_left(remainder, WHITESPACE_CHARS); if remainder.count { log_error("Unexpected trailing characters: %", remainder); return false, result; } return true, result; } json_parse_file :: (filename: string, $T: Type, ignore_unknown := true, rename := rename_by_note) -> success: bool, T { file_data, success := read_entire_file(filename); result: T; if !success { log_error("Could not read file: %", filename); return false, result; } defer free(file_data); if (context.log_level >= .VERBOSE) { log("Read file: %", success); } success, result = json_parse_string(file_data, T, ignore_unknown, rename=rename); return success, result; } json_write_native :: (builder: *String_Builder, data: *void, info: *Type_Info, indent_char := "\t", ignore := ignore_by_note, rename := rename_by_note, level := 0) { if info.type == { case .BOOL; append(builder, ifx (.*)(cast(*bool) data) "true" else "false"); case .INTEGER; #through; case .FLOAT; any_val: Any; any_val.type = info; any_val.value_pointer = data; print_item_to_builder(builder, any_val); case .ENUM; any_val: Any; any_val.type = info; any_val.value_pointer = data; append(builder, #char "\""); print_item_to_builder(builder, any_val); append(builder, #char "\""); case .STRING; json_append_escaped(builder, (.*)(cast(*string) data)); case .ARRAY; info_array := cast(*Type_Info_Array) info; element_size := info_array.element_type.runtime_size; assert(element_size != -1); stride := element_size; array_data := data; array_count := info_array.array_count; if info_array.array_count == -1 { array_count = (.*) cast(*s64) data; array_dest: **void = data + 8; array_data = (.*) array_dest; } append(builder, "["); if array_data { if indent_char.count { append(builder, "\n"); for 0..level append(builder, indent_char); } for 0..array_count-1 { json_write_native(builder, array_data, info_array.element_type, indent_char, ignore, rename, level + 1); if it != array_count - 1 append(builder, ","); array_data += stride; } } if indent_char.count { append(builder, "\n"); for 0..level-1 append(builder, indent_char); } append(builder, "]"); case .STRUCT; struct_info := cast(*Type_Info_Struct) info; if is_generic_json_value(info) { value := cast(*JSON_Value) data; json_write_json_value(builder, (.*)value, indent_char, level); } else { append(builder, #char "{"); first := true; json_write_native_members(builder, data, struct_info.members, indent_char, ignore, rename, level, *first); if indent_char.count { append(builder, "\n"); for 0..level-1 append(builder, indent_char); } append(builder, "}"); } case .POINTER; ptr_info := cast(*Type_Info_Pointer) info; ptr := (.*) cast(**void) data; if ptr { json_write_native(builder, ptr, ptr_info.pointer_to, indent_char, ignore, rename, level); } else { append(builder, "null"); } case; assert(false, "Unsupported type: %", info.type); } } #scope_file json_write_native_members :: (builder: *String_Builder, data: *void, members: [] Type_Info_Struct_Member, indent_char := "\t", ignore := ignore_by_note, rename: Rename_Proc, level := 0, first: *bool) { for * member: members { if member.flags & .CONSTANT continue; if ignore(member) continue; if (member.type.type == .STRUCT && member.flags & .USING) { info := cast(*Type_Info_Struct) member.type; json_write_native_members(builder, data + member.offset_in_bytes, info.members, indent_char, ignore, rename, level, first); } else { if !(.*)first append(builder, ","); (.*)first = false; if indent_char.count { append(builder, "\n"); for 0..level append(builder, indent_char); } renamed_name := rename(member); name := ifx renamed_name.count > 0 renamed_name else member.name; json_append_escaped(builder, name); append(builder, ": "); json_write_native(builder, data + member.offset_in_bytes, member.type, indent_char, ignore, rename, level + 1); } } } is_generic_json_value_or_pointer_to_it :: (info: *Type_Info) -> bool { value_info := info; if info.type == .POINTER { pointer_info := cast(*Type_Info_Pointer) info; value_info = pointer_info.pointer_to; } return is_generic_json_value(info); } is_generic_json_value :: (info: *Type_Info) -> bool { return info == type_info(JSON_Value); } parse_value :: (to_parse: string, slot: *u8, info: *Type_Info, ignore_unknown: bool, field_name: string, rename: Rename_Proc) -> remainder: string, success: bool { remainder := trim_left(to_parse, WHITESPACE_CHARS); success := true; prepare_slot :: (expected_type: Type_Info_Tag, info: *Type_Info, slot: *u8, to_parse: string) -> *u8, success: bool, is_generic: bool, info: *Type_Info { value_info := info; if info.type == .POINTER { pointer_info := cast(*Type_Info_Pointer) info; value_info = pointer_info.pointer_to; } if info.type == .ENUM { info_enum := cast(*Type_Info_Enum)info; value_info = info_enum.internal_type; } is_generic := is_generic_json_value(value_info); if !is_generic && value_info.type != expected_type { teaser := to_parse; if teaser.count > 50 teaser.count = 50; builder: String_Builder; print_type_to_builder(*builder, info); type_name := builder_to_string(*builder,, temp); log_error("Cannot parse % value into type \"%\". Remaining input is: %…", expected_type, type_name, teaser); return null, false, false, value_info; } if info.type == .POINTER { value_slot := alloc(value_info.runtime_size); initializer: (*void) #no_context; if value_info.type == .STRUCT { struct_info := cast(*Type_Info_Struct) value_info; initializer = struct_info.initializer; } if initializer { initializer(value_slot); } else { memset(value_slot, 0, value_info.runtime_size); } (.*)cast(**u8)slot = value_slot; return value_slot, true, is_generic, value_info; } else { return slot, true, is_generic, value_info; } } is_generic: bool; if remainder[0] == { case #char "n"; remainder, success = expect_and_slice(remainder, "null"); if !success return remainder, false; if slot { if info.type == .POINTER { (.*)cast(**void) slot = null; } else { builder: String_Builder; print_type_to_builder(*builder, info); type_name := builder_to_string(*builder,, temp); log_error("Got NULL value for non-pointer type \"%\" of field \"%\". Keeping default value instead.", type_name, field_name); } } return remainder, true; case #char "t"; remainder, success = expect_and_slice(remainder, "true"); if !success return remainder, false; if slot { value_slot: *u8; value_slot, success, is_generic = prepare_slot(.BOOL, info, slot, to_parse); if success { if is_generic { json_set(cast(*JSON_Value)value_slot, true); } else { (.*)cast(*bool)value_slot = true; } } } case #char "f"; remainder, success = expect_and_slice(remainder, "false"); if !success return remainder, false; if slot { value_slot: *u8; value_slot, success, is_generic = prepare_slot(.BOOL, info, slot, to_parse); if success { if is_generic { json_set(cast(*JSON_Value)value_slot, false); } else { (.*)cast(*bool)value_slot = false; } } } case #char "\""; if slot && info && info.type == .ENUM { info_enum := cast(*Type_Info_Enum)info; value_slot: *u8; value_slot, success, is_generic = prepare_slot(.INTEGER, info_enum.internal_type, slot, to_parse); remainder, success = parse_enum_string(remainder, value_slot, info_enum); } else { value: string; value, remainder, success = parse_string(remainder); stored := false; defer if !stored free(value); if success && slot { value_slot: *u8; value_slot, success, is_generic = prepare_slot(.STRING, info, slot, to_parse); if success { if is_generic { json_set(cast(*JSON_Value)value_slot, value); } else { (.*)cast(*string)value_slot = value; } stored = true; } } } case #char "["; value_slot: *u8; value_info: *Type_Info; if slot { value_slot, success, is_generic, value_info = prepare_slot(.ARRAY, info, slot, to_parse); } if success { if is_generic { value: [] JSON_Value; value, remainder, success = parse_array(remainder); json_set(cast(*JSON_Value)value_slot, value); } else { remainder, success = parse_array(remainder, value_slot, cast(*Type_Info_Array) value_info, ignore_unknown, rename=rename); } } case #char "{"; value_slot: *u8; value_info: *Type_Info; if slot { value_slot, success, is_generic, value_info = prepare_slot(.STRUCT, info, slot, to_parse); } if success { if is_generic { value := New(JSON_Object); (.*)value, remainder, success = parse_object(remainder); json_set(cast(*JSON_Value)value_slot, value); } else { remainder, success = parse_object(remainder, value_slot, cast(*Type_Info_Struct) value_info, ignore_unknown, rename=rename); } } case; if slot == null || info.type == .FLOAT || is_generic_json_value_or_pointer_to_it(info) { float_value: float64; float_value, success, remainder = string_to_float64(remainder); if success && slot { value_slot: *u8; value_info: *Type_Info; value_slot, success, is_generic, value_info = prepare_slot(.FLOAT, info, slot, to_parse); if success { if is_generic { json_set(cast(*JSON_Value)value_slot, float_value); } else { if value_info.runtime_size == 4 { ((.*) cast(*float) slot) = cast(float) float_value; } else { assert(value_info.runtime_size == 8); ((.*) cast(*float64) slot) = float_value; } } } } } else { if slot { value_slot: *u8; value_info: *Type_Info; value_slot, success, is_generic, value_info = prepare_slot(.INTEGER, info, slot, to_parse); if success { if is_generic { int_value: s64; int_value, success, remainder = string_to_int(remainder, T = s64); if success { json_set(cast(*JSON_Value)value_slot, int_value); } else { log_error("Could not parse \"%\" as an integer.", to_parse); } } else { info_int := cast(*Type_Info_Integer) value_info; success, remainder = parse_and_write_integer(info_int, value_slot, to_parse); } } } else { int_value: s64; int_value, success, remainder = string_to_int(remainder, T = s64); if !success { log_error("Could not parse \"%\" as an integer.", to_parse); } } } } return remainder, success; } parse_enum_string :: (str: string, slot: *u8, info_enum: *Type_Info_Enum) -> remainder: string, success: bool { value, remainder, success := parse_string(str); defer free(value); if !success return remainder, false; // Parse by members' names normalize_enum_value :: inline (name: string) -> string #expand { normalized := trim(name); if normalized.count > info_enum.name.count && starts_with(normalized, info_enum.name) && normalized[info_enum.name.count] == #char "." { normalized = slice(normalized, info_enum.name.count+1, normalized.count-info_enum.name.count-1); } else if starts_with(normalized, ".") { normalized = slice(normalized, 1, normalized.count-1); } return normalized; } int_info := info_enum.internal_type; int_value: s64; if info_enum.enum_type_flags & .FLAGS { values := split(value, "|",, temp); for v: values { name := normalize_enum_value(v); found_name := false; for info_enum.names { if name == it { found_name = true; int_value |= info_enum.values[it_index]; break; } } if !found_name { log_error("Enum \"%\" does not contain a member named \"%\".", info_enum.name, name); success = false; } } } else { success = false; name := normalize_enum_value(value); for info_enum.names { if name == it { int_value = info_enum.values[it_index]; success = true; break; } } if !success { log_error("Enum \"%\" does not contain a member named \"%\".", info_enum.name, name); } } if success { if int_info.signed { valid, low, high := Reflection.range_check_and_store(int_value, int_info, slot); if !valid { log_error("The value '%' is out of range. (It must be between % and %.)", int_value, low, high); return remainder, false; } } else { valid, low, high := Reflection.range_check_and_store(cast(u64) int_value, int_info, slot); if !valid { log_error("The value '%' is out of range. (It must be between % and %.)", int_value, low, high); return remainder, false; } } } return remainder, success; } parse_array :: (str: string, slot: *u8, info: *Type_Info_Array, ignore_unknown: bool, rename: Rename_Proc) -> remainder: string, success: bool { element_size: int; if slot { element_size = info.element_type.runtime_size; assert(element_size != -1, "Unknown element size"); } assert(str[0] == #char "[", "Invalid object start %", str); remainder := advance(str); remainder = trim_left(remainder, WHITESPACE_CHARS); if remainder[0] == #char "]" { remainder = advance(remainder); // @Robustness: Do we need to zero out the array? return remainder, true; } if slot { array: Resizable_Array; initializer: (*void) #no_context; if info.element_type.type == .STRUCT { struct_info := cast(*Type_Info_Struct) info.element_type; initializer = struct_info.initializer; } while true { maybe_grow(*array, element_size); element_data := array.data + array.count * element_size; if initializer { initializer(element_data); } else { memset(element_data, 0, element_size); } success: bool; remainder, success = parse_value(remainder, element_data, info.element_type, ignore_unknown, "", rename=rename); if !success return remainder, false; array.count += 1; remainder = trim_left(remainder, WHITESPACE_CHARS); if remainder[0] != #char "," break; remainder = advance(remainder); remainder = trim_left(remainder, WHITESPACE_CHARS); } if info.array_type == .VIEW { view := (cast(*Array_View_64) slot); view.count = array.count; view.data = array.data; } else if info.array_count == -1 { // Resizable array (.*)(cast(*Resizable_Array) slot) = array; } else { // Fixed-size array if (info.array_count != array.count) { log_error("Expected array of size %, but found array of size %\n", info.array_count, array.count); return remainder, false; } memcpy(slot, array.data, array.count * element_size); } } else { while true { success: bool; remainder, success = parse_value(remainder, null, null, ignore_unknown, "", rename=rename); if !success return remainder, false; remainder = trim_left(remainder, WHITESPACE_CHARS); if remainder[0] != #char "," break; remainder = advance(remainder); remainder = trim_left(remainder, WHITESPACE_CHARS); } } if remainder[0] != #char "]" return remainder, false; remainder = advance(remainder); return remainder, true; } Member_Offset :: struct { member: *Type_Info_Struct_Member; offset_in_bytes: s64; } // This procedure is somewhat copied from Basic.get_field. fill_member_table :: (table: *Table(string, Member_Offset), info: *Type_Info_Struct, rename: Rename_Proc, base_offset := 0) { for * member: info.members { offset := base_offset + member.offset_in_bytes; name := rename(member); assert(!table_find_pointer(table, name), "Redeclaration of member \"%\": % vs. %", name, (.*)member, (.*)table_find_pointer(table, name)); table_set(table, name, .{member, offset}); if (member.flags & .USING) && (member.type.type == .STRUCT) { fill_member_table(table, cast(*Type_Info_Struct)member.type, rename, offset); } } } parse_object :: (str: string, slot: *u8, info: *Type_Info_Struct, ignore_unknown: bool, rename: Rename_Proc) -> remainder: string, success: bool { assert(str[0] == #char "{", "Invalid object start %", str); remainder := advance(str); remainder = trim_left(remainder, WHITESPACE_CHARS); if remainder[0] == #char "}" { remainder = advance(remainder); return remainder, true; } // @Speed: Building this table every time is pretty silly. // We should probably either not build it at all or cache it somewhere. member_table: Table(string, Member_Offset); init(*member_table); defer deinit(*member_table); if info fill_member_table(*member_table, info, rename); while true { if remainder[0] != #char "\"" return remainder, false; key: string; success: bool; key, remainder, success = parse_string(remainder); if !success return remainder, false; defer free(key); member_found, member_offset := table_find(*member_table, key); member_slot: *u8; member_info: *Type_Info; if member_found { member_slot = slot + member_offset.offset_in_bytes; member_info = member_offset.member.type; } else if !ignore_unknown { log_error("Missing member % in %", key, (.*)info); return remainder, false; } remainder = trim_left(remainder, WHITESPACE_CHARS); if remainder[0] != #char ":" return remainder, false; remainder = advance(remainder); remainder = trim_left(remainder, WHITESPACE_CHARS); remainder, success = parse_value(remainder, member_slot, member_info, ignore_unknown, key, rename); if !success return remainder, false; remainder = trim_left(remainder, WHITESPACE_CHARS); if remainder[0] != #char "," break; remainder = advance(remainder); remainder = trim_left(remainder, WHITESPACE_CHARS); } if remainder[0] != #char "}" return remainder, false; remainder = advance(remainder); return remainder, true; } parse_and_write_integer :: (info: *Type_Info_Integer, pointer: *void, string_value: string) -> bool, remainder: string { if info.signed { success, remainder := parse_and_write_integer(info, pointer, string_value, signed = true); return success, remainder; } else { success, remainder := parse_and_write_integer(info, pointer, string_value, signed = false); return success, remainder; } } parse_and_write_integer :: (info: *Type_Info_Integer, pointer: *void, string_value: string, $signed: bool) -> bool, remainder: string { #if signed { Int_Type :: s64; } else { Int_Type :: u64; } int_value, int_success, remainder := string_to_int(string_value, T = Int_Type); if !int_success { #if signed { log_error("Could not parse \"%\" as an integer.", string_value); } else { log_error("Could not parse \"%\" as an unsigned integer.", string_value); } return false, remainder; } valid, low, high := Reflection.range_check_and_store(int_value, info, pointer); if !valid { log_error("The value '%' is out of range. (It must be between % and %.)", int_value, low, high); return false, remainder; } return true, remainder; } Reflection :: #import "Reflection";