// 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, < 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); } < 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, < 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_new(*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, < 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";