// 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, float_handling := Special_Float_Handling.SUPPORT_NULL) -> 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, float_handling=float_handling); 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, float_handling := Special_Float_Handling.SUPPORT_NULL) -> 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, float_handling=float_handling); 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, float_handling := Special_Float_Handling.SUPPORT_NULL, level := 0) { if info.type == { case .BOOL; append(builder, ifx (.*)(cast(*bool) data) "true" else "false"); case .INTEGER; any_val: Any; any_val.type = info; any_val.value_pointer = data; print_item_to_builder(builder, any_val); case .FLOAT; is_nan, is_infinity, is_negative: bool; if info.runtime_size == 4 { is_nan, is_infinity, is_negative = get_nan_or_infinity((cast(*u32) data).*); } else if info.runtime_size == 8 { is_nan, is_infinity, is_negative = get_nan_or_infinity((cast(*u64) data).*); } if is_nan || is_infinity { append_special_float(builder, float_handling, is_nan, is_negative); } else { 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 { for 0..array_count-1 { if indent_char.count { append(builder, "\n"); for 0..level append(builder, indent_char); } json_write_native(builder, array_data, info_array.element_type, indent_char, ignore, rename, float_handling=float_handling, 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, float_handling, 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, float_handling, 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, float_handling: Special_Float_Handling, 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, float_handling, 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, float_handling, 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, float_handling: Special_Float_Handling) -> remainder: string, success: bool { remainder := trim_left(to_parse, WHITESPACE_CHARS); if !remainder return "", false; 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; } } store_float_value :: (info: *Type_Info, slot: *u8, to_parse: string, value: float64) -> bool { 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, value); } else { if value_info.runtime_size == 4 { ((.*) cast(*float) slot) = cast(float) value; } else { assert(value_info.runtime_size == 8); ((.*) cast(*float64) slot) = value; } } } return success; } 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 if info.type == .FLOAT && float_handling & .SUPPORT_NULL { success = store_float_value(info, slot, to_parse, NAN); } else { builder: String_Builder; print_type_to_builder(*builder, info); type_name := builder_to_string(*builder,, temp); log_error("Found 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 { if info && info.type == .FLOAT && (float_handling & .SUPPORT_STRINGS) { if value == { case "NaN"; success = store_float_value(info, slot, to_parse, NAN); case "Infinity"; success = store_float_value(info, slot, to_parse, INFINITY); case "-Infinity"; success = store_float_value(info, slot, to_parse, NEGATIVE_INFINITY); case; builder: String_Builder; print_type_to_builder(*builder, info); type_name := builder_to_string(*builder,, temp); log_error("Found unsupported string value \"%\" for field \"%\" of type %.", value, field_name, type_name); } } else { 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, float_handling=float_handling); } } 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, float_handling=float_handling); } } 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 { success = store_float_value(info, slot, to_parse, 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, float_handling: Special_Float_Handling) -> 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 return "", false; success := 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 { if remainder[0] == #char "]" break; if array.count { if remainder[0] != #char "," { success = false; break; } remainder = advance(remainder); remainder = trim_left(remainder, WHITESPACE_CHARS); if !remainder { success = false; break; } if remainder[0] == #char "]" { // Special case for trailing comma. This would also fail in parse_value below, but with a weird error. success = false; break; } } if info.array_count != -1 && array.count >= info.array_count { log_error("Expected array of size %, but found array of with at least % elements\n", info.array_count, info.array_count + 1); success = false; break; } maybe_grow(*array, element_size); element_data := array.data + array.count * element_size; array.count += 1; if initializer { initializer(element_data); } else { memset(element_data, 0, element_size); } remainder, success = parse_value(remainder, element_data, info.element_type, ignore_unknown, "", rename=rename, float_handling=float_handling); if !success break; remainder = trim_left(remainder, WHITESPACE_CHARS); if !remainder { success = false; break; } } 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); success = false; // Fill up the rest of the array so that we can safely copy the data. Maybe this is unnecessary, but it allows to print the successfully parsed values even in case of an error. while array.count < info.array_count { maybe_grow(*array, element_size); array.count += 1; element_data := array.data + array.count * element_size; if initializer { initializer(element_data); } else { memset(element_data, 0, element_size); } } } assert(info.array_count == array.count); memcpy(slot, array.data, array.count * element_size); free(array.data); } } else { first := true; while true { if remainder[0] == #char "]" break; if !first { if remainder[0] != #char "," return remainder, false; remainder = advance(remainder); } first = false; remainder, success = parse_value(remainder, null, null, ignore_unknown, "", rename=rename, float_handling=float_handling); if !success return remainder, false; remainder = trim_left(remainder, WHITESPACE_CHARS); if !remainder return "", false; } } if !remainder || remainder[0] != #char "]" return remainder, false; remainder = advance(remainder); return remainder, success; } 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, float_handling: Special_Float_Handling) -> remainder: string, success: bool { assert(str[0] == #char "{", "Invalid object start %", str); remainder := advance(str); remainder = trim_left(remainder, WHITESPACE_CHARS); if !remainder return "", false; 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); defer table_deinit(*member_table); if info fill_member_table(*member_table, info, rename); while true { if !remainder || 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 || remainder[0] != #char ":" return remainder, false; remainder = advance(remainder); remainder, success = parse_value(remainder, member_slot, member_info, ignore_unknown, key, rename, float_handling=float_handling); if !success return remainder, false; remainder = trim_left(remainder, WHITESPACE_CHARS); if !remainder || remainder[0] != #char "," break; remainder = advance(remainder); remainder = trim_left(remainder, WHITESPACE_CHARS); } if !remainder || 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";