diff options
| author | Jeroen van Rijn <Kelimion@users.noreply.github.com> | 2025-12-10 17:54:12 +0100 |
|---|---|---|
| committer | Jeroen van Rijn <Kelimion@users.noreply.github.com> | 2025-12-10 17:54:12 +0100 |
| commit | 83fb7ba9523bc67b9f3d72ba961abd3a55dffda0 (patch) | |
| tree | 2b96f651d82910a99e268f0112411101b125ae86 | |
| parent | 503269b9bf896d4263c90e76ce65dbfc1fadb27e (diff) | |
Make custom JSON (un)marshalers more like core:fmt's
| -rw-r--r-- | core/encoding/json/marshal.odin | 120 | ||||
| -rw-r--r-- | core/encoding/json/unmarshal.odin | 141 |
2 files changed, 200 insertions, 61 deletions
diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index 58fbbd852..93d0b523e 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -62,10 +62,79 @@ Marshal_Options :: struct { mjson_skipped_first_braces_end: bool, } -User_Marshaller :: #type proc(w: io.Writer, v: any) -> Marshal_Error -User_Marshaller_Map :: map[typeid]User_Marshaller +User_Marshaler :: #type proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> Marshal_Error -marshal :: proc(v: any, opt: Marshal_Options = {}, user_marshallers: User_Marshaller_Map = nil, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Marshal_Error) { +Register_User_Marshaler_Error :: enum { + None, + No_User_Marshaler, + Marshaler_Previously_Found, +} + +// Example User Marshaler: +// Custom Marshaler for `int` +// Some_Marshaler :: proc(w: io.Writer, v: any, opt: ^json.Marshal_Options) -> json.Marshal_Error { +// io.write_string(w, fmt.tprintf("%b", v)) +// return json.Marshal_Data_Error.None +// } +// +// main :: proc() { +// // Ensure the json._user_marshaler map is initialized +// json.set_user_marshalers(new(map[typeid]json.User_Marshaler)) +// reg_err := json.register_user_marshaler(type_info_of(int).id, Some_Marshaler) +// assert(reg_err == .None) +// +// +// // Use the custom marshaler +// SomeType :: struct { +// value: int, +// } +// +// x := SomeType{42} +// data, marshal_err := json.marshal(x) +// assert(marshal_err == nil) +// defer delete(data) +// +// fmt.println("Custom output:", string(data)) // Custom output: {"value":101010} +// } + +// NOTE(Jeroen): This is a pointer to prevent accidental additions +// it is prefixed with `_` rather than marked with a private attribute so that users can access it if necessary +_user_marshalers: ^map[typeid]User_Marshaler + +// Sets user-defined marshalers for custom json marshaling of specific types +// +// Inputs: +// - m: A pointer to a map of typeids to User_Marshaler procs. +// +// NOTE: Must be called before using register_user_marshaler. +// +set_user_marshalers :: proc(m: ^map[typeid]User_Marshaler) { + assert(_user_marshalers == nil, "set_user_marshalers must not be called more than once.") + _user_marshalers = m +} + +// Registers a user-defined marshaler for a specific typeid +// +// Inputs: +// - id: The typeid of the custom type. +// - formatter: The User_Marshaler function for the custom type. +// +// Returns: A Register_User_Marshaler_Error value indicating the success or failure of the operation. +// +// WARNING: set_user_marshalers must be called before using this procedure. +// +register_user_marshaler :: proc(id: typeid, marshaler: User_Marshaler) -> Register_User_Marshaler_Error { + if _user_marshalers == nil { + return .No_User_Marshaler + } + if prev, found := _user_marshalers[id]; found && prev != nil { + return .Marshaler_Previously_Found + } + _user_marshalers[id] = marshaler + return .None +} + +marshal :: proc(v: any, opt: Marshal_Options = {}, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Marshal_Error) { b := strings.builder_make(allocator, loc) defer if err != nil { strings.builder_destroy(&b) @@ -75,7 +144,7 @@ marshal :: proc(v: any, opt: Marshal_Options = {}, user_marshallers: User_Marsha runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator) opt := opt - marshal_to_builder(&b, v, &opt, user_marshallers) or_return + marshal_to_builder(&b, v, &opt) or_return if len(b.buf) != 0 { data = b.buf[:] @@ -84,21 +153,20 @@ marshal :: proc(v: any, opt: Marshal_Options = {}, user_marshallers: User_Marsha return data, nil } -marshal_to_builder :: proc(b: ^strings.Builder, v: any, opt: ^Marshal_Options, user_marshallers: User_Marshaller_Map = nil) -> Marshal_Error { - return marshal_to_writer(strings.to_writer(b), v, opt, user_marshallers) +marshal_to_builder :: proc(b: ^strings.Builder, v: any, opt: ^Marshal_Options) -> Marshal_Error { + return marshal_to_writer(strings.to_writer(b), v, opt) } -marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options, user_marshallers: User_Marshaller_Map = nil) -> (err: Marshal_Error) { +marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: Marshal_Error) { if v == nil { io.write_string(w, "null") or_return return } - if user_marshallers != nil { - marshaller := user_marshallers[v.id] - if marshaller != nil { - marshaller(w, v) or_return - return + if _user_marshalers != nil { + marshaler := _user_marshalers[v.id] + if marshaler != nil { + return marshaler(w, v, opt) } } @@ -219,7 +287,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options, user_mars for i in 0..<info.count { opt_write_iteration(w, opt, i == 0) or_return data := uintptr(v.data) + uintptr(i*info.elem_size) - marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt, user_marshallers) or_return + marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return } opt_write_end(w, opt, ']') or_return @@ -238,7 +306,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options, user_mars opt_write_iteration(w, opt, i == 0) or_return opt_write_key(w, opt, enum_type.names[index]) or_return data := uintptr(v.data) + uintptr(i*info.elem_size) - marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt, user_marshallers) or_return + marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return } opt_write_end(w, opt, '}') or_return @@ -248,7 +316,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options, user_mars for i in 0..<array.len { opt_write_iteration(w, opt, i == 0) or_return data := uintptr(array.data) + uintptr(i*info.elem_size) - marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt, user_marshallers) or_return + marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return } opt_write_end(w, opt, ']') or_return @@ -258,7 +326,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options, user_mars for i in 0..<slice.len { opt_write_iteration(w, opt, i == 0) or_return data := uintptr(slice.data) + uintptr(i*info.elem_size) - marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt, user_marshallers) or_return + marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return } opt_write_end(w, opt, ']') or_return @@ -308,7 +376,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options, user_mars } } - marshal_to_writer(w, any{value, info.value.id}, opt, user_marshallers) or_return + marshal_to_writer(w, any{value, info.value.id}, opt) or_return } } else { Entry :: struct { @@ -351,7 +419,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options, user_mars for s, i in sorted { opt_write_iteration(w, opt, i == 0) or_return opt_write_key(w, opt, s.key) or_return - marshal_to_writer(w, s.value, opt, user_marshallers) or_return + marshal_to_writer(w, s.value, opt) or_return } } } @@ -395,7 +463,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options, user_mars return false } - marshal_struct_fields :: proc(w: io.Writer, v: any, opt: ^Marshal_Options, user_marshallers: User_Marshaller_Map) -> (err: Marshal_Error) { + marshal_struct_fields :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: Marshal_Error) { ti := runtime.type_info_base(type_info_of(v.id)) info := ti.variant.(runtime.Type_Info_Struct) first_iteration := true @@ -430,7 +498,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options, user_mars } else { // Marshal the fields of 'using _: T' fields directly into the parent struct if info.usings[i] && name == "_" { - marshal_struct_fields(w, the_value, opt, user_marshallers) or_return + marshal_struct_fields(w, the_value, opt) or_return continue } else { opt_write_key(w, opt, name) or_return @@ -438,13 +506,13 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options, user_mars } - marshal_to_writer(w, the_value, opt, user_marshallers) or_return + marshal_to_writer(w, the_value, opt) or_return } return } opt_write_start(w, opt, '{') or_return - marshal_struct_fields(w, v, opt, user_marshallers) or_return + marshal_struct_fields(w, v, opt) or_return opt_write_end(w, opt, '}') or_return case runtime.Type_Info_Union: @@ -477,17 +545,17 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options, user_mars tag -= 1 } id := info.variants[tag].id - return marshal_to_writer(w, any{v.data, id}, opt, user_marshallers) + return marshal_to_writer(w, any{v.data, id}, opt) case runtime.Type_Info_Enum: if !opt.use_enum_names || len(info.names) == 0 { - return marshal_to_writer(w, any{v.data, info.base.id}, opt, user_marshallers) + return marshal_to_writer(w, any{v.data, info.base.id}, opt) } else { name, found := reflect.enum_name_from_value_any(v) if found { - return marshal_to_writer(w, name, opt, user_marshallers) + return marshal_to_writer(w, name, opt) } else { - return marshal_to_writer(w, any{v.data, info.base.id}, opt, user_marshallers) + return marshal_to_writer(w, any{v.data, info.base.id}, opt) } } diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index 564b84842..25209439f 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -26,10 +26,81 @@ Unmarshal_Error :: union { Unsupported_Type_Error, } -User_Unmarshaller :: #type proc(p: ^Parser, v: any) -> Unmarshal_Error -User_Unmarshaller_Map :: map[typeid]User_Unmarshaller +User_Unmarshaler :: #type proc(p: ^Parser, v: any) -> Unmarshal_Error -unmarshal_any :: proc(data: []byte, v: any, spec := DEFAULT_SPECIFICATION, user_unmarshallers: User_Unmarshaller_Map, allocator := context.allocator) -> Unmarshal_Error { +Register_User_Unmarshaler_Error :: enum { + None, + No_User_Unmarshaler, + Unmarshaler_Previously_Found, +} + +// Example User Unmarshaler: +// Custom Unmarshaler for `int` +// Some_Unmarshaler :: proc(p: ^json.Parser, v: any) -> json.Unmarshal_Error { +// token := p.curr_token.text +// i, ok := strconv.parse_i64_of_base(token, 2) +// if !ok { +// return .Invalid_Data +// +// } +// (^int)(v.data)^ = int(i) +// return .None +// } +// +// _main :: proc() { +// // Ensure the json._user_unmarshaler map is initialized +// json.set_user_unmarshalers(new(map[typeid]json.User_Unmarshaler)) +// reg_err := json.register_user_unmarshaler(type_info_of(int).id, Some_Unmarshaler) +// assert(reg_err == .None) +// +// data := `{"value":101010}` +// SomeType :: struct { +// value: int, +// } +// y: SomeType +// +// unmarshal_err := json.unmarshal(transmute([]byte)data, &y) +// fmt.println(y, unmarshal_err) +// } + +// NOTE(Jeroen): This is a pointer to prevent accidental additions +// it is prefixed with `_` rather than marked with a private attribute so that users can access it if necessary +_user_unmarshalers: ^map[typeid]User_Unmarshaler + +// Sets user-defined unmarshalers for custom json unmarshaling of specific types +// +// Inputs: +// - m: A pointer to a map of typeids to User_Unmarshaler procs. +// +// NOTE: Must be called before using register_user_unmarshaler. +// +set_user_unmarshalers :: proc(m: ^map[typeid]User_Unmarshaler) { + assert(_user_unmarshalers == nil, "set_user_unmarshalers must not be called more than once.") + _user_unmarshalers = m +} + +// Registers a user-defined unmarshaler for a specific typeid +// +// Inputs: +// - id: The typeid of the custom type. +// - unmarshaler: The User_Unmarshaler function for the custom type. +// +// Returns: A Register_User_Unmarshaler_Error value indicating the success or failure of the operation. +// +// WARNING: set_user_unmarshalers must be called before using this procedure. +// +register_user_unmarshaler :: proc(id: typeid, unmarshaler: User_Unmarshaler) -> Register_User_Unmarshaler_Error { + if _user_unmarshalers == nil { + return .No_User_Unmarshaler + } + if prev, found := _user_unmarshalers[id]; found && prev != nil { + return .Unmarshaler_Previously_Found + } + _user_unmarshalers[id] = unmarshaler + return .None +} + +unmarshal_any :: proc(data: []byte, v: any, spec := DEFAULT_SPECIFICATION, allocator := context.allocator) -> Unmarshal_Error { v := v if v == nil || v.id == nil { return .Invalid_Parameter @@ -40,8 +111,10 @@ unmarshal_any :: proc(data: []byte, v: any, spec := DEFAULT_SPECIFICATION, user_ return .Non_Pointer_Parameter } PARSE_INTEGERS :: true - - if !is_valid(data, spec, PARSE_INTEGERS) { + + // If we have custom unmarshalers, we skip validation in case the custom data is not quite up to spec. + have_custom := _user_unmarshalers != nil && len(_user_unmarshalers) > 0 + if !have_custom && !is_valid(data, spec, PARSE_INTEGERS) { return .Invalid_Data } p := make_parser(data, spec, PARSE_INTEGERS, allocator) @@ -56,20 +129,20 @@ unmarshal_any :: proc(data: []byte, v: any, spec := DEFAULT_SPECIFICATION, user_ if p.spec == .MJSON { #partial switch p.curr_token.kind { case .Ident, .String: - return unmarshal_object(&p, data, .EOF, user_unmarshallers) + return unmarshal_object(&p, data, .EOF) } } - return unmarshal_value(&p, data, user_unmarshallers) + return unmarshal_value(&p, data) } -unmarshal :: proc(data: []byte, ptr: ^$T, spec := DEFAULT_SPECIFICATION, user_unmarshallers: User_Unmarshaller_Map = nil, allocator := context.allocator) -> Unmarshal_Error { - return unmarshal_any(data, ptr, spec, user_unmarshallers, allocator) +unmarshal :: proc(data: []byte, ptr: ^$T, spec := DEFAULT_SPECIFICATION, allocator := context.allocator) -> Unmarshal_Error { + return unmarshal_any(data, ptr, spec, allocator) } -unmarshal_string :: proc(data: string, ptr: ^$T, spec := DEFAULT_SPECIFICATION, user_unmarshallers: User_Unmarshaller_Map = nil, allocator := context.allocator) -> Unmarshal_Error { - return unmarshal_any(transmute([]byte)data, ptr, spec, user_unmarshallers, allocator) +unmarshal_string :: proc(data: string, ptr: ^$T, spec := DEFAULT_SPECIFICATION, allocator := context.allocator) -> Unmarshal_Error { + return unmarshal_any(transmute([]byte)data, ptr, spec, allocator) } @@ -268,17 +341,15 @@ unmarshal_string_token :: proc(p: ^Parser, val: any, token: Token, ti: ^reflect. return false, nil } - @(private) -unmarshal_value :: proc(p: ^Parser, v: any, user_unmarshallers: User_Unmarshaller_Map = nil) -> (err: Unmarshal_Error) { +unmarshal_value :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) { UNSUPPORTED_TYPE := Unsupported_Type_Error{v.id, p.curr_token} token := p.curr_token - if user_unmarshallers != nil { - unmarshaller := user_unmarshallers[v.id] - if unmarshaller != nil { - unmarshaller(p, v) or_return - return + if _user_unmarshalers != nil { + unmarshaler := _user_unmarshalers[v.id] + if unmarshaler != nil { + return unmarshaler(p, v) } } @@ -298,7 +369,7 @@ unmarshal_value :: proc(p: ^Parser, v: any, user_unmarshallers: User_Unmarshalle for variant, i in u.variants { variant_any := any{v.data, variant.id} variant_p := p^ - if err = unmarshal_value(&variant_p, variant_any, user_unmarshallers); err == nil { + if err = unmarshal_value(&variant_p, variant_any); err == nil { p^ = variant_p raw_tag := i @@ -374,10 +445,10 @@ unmarshal_value :: proc(p: ^Parser, v: any, user_unmarshallers: User_Unmarshalle return UNSUPPORTED_TYPE case .Open_Brace: - return unmarshal_object(p, v, .Close_Brace, user_unmarshallers) + return unmarshal_object(p, v, .Close_Brace) case .Open_Bracket: - return unmarshal_array(p, v, user_unmarshallers) + return unmarshal_array(p, v) case: if p.spec != .JSON { @@ -434,7 +505,7 @@ json_name_from_tag_value :: proc(value: string) -> (json_name, extra: string) { @(private) -unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind, user_unmarshallers: User_Unmarshaller_Map = nil) -> (err: Unmarshal_Error) { +unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unmarshal_Error) { UNSUPPORTED_TYPE := Unsupported_Type_Error{v.id, p.curr_token} if end_token == .Close_Brace { @@ -533,7 +604,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind, user_unmarsh field_ptr := rawptr(uintptr(v.data) + offset) field := any{field_ptr, type.id} - unmarshal_value(p, field, user_unmarshallers) or_return + unmarshal_value(p, field) or_return if parse_comma(p) { break struct_loop @@ -575,7 +646,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind, user_unmarsh mem.zero_slice(elem_backing) - if uerr := unmarshal_value(p, map_backing_value, user_unmarshallers); uerr != nil { + if uerr := unmarshal_value(p, map_backing_value); uerr != nil { delete(key, p.allocator) return uerr } @@ -637,7 +708,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind, user_unmarsh index_ptr := rawptr(uintptr(v.data) + uintptr(index*t.elem_size)) index_any := any{index_ptr, t.elem.id} - unmarshal_value(p, index_any, user_unmarshallers) or_return + unmarshal_value(p, index_any) or_return if parse_comma(p) { break enumerated_array_loop @@ -673,8 +744,8 @@ unmarshal_count_array :: proc(p: ^Parser) -> (length: uintptr) { } @(private) -unmarshal_array :: proc(p: ^Parser, v: any, user_unmarshallers: User_Unmarshaller_Map = nil) -> (err: Unmarshal_Error) { - assign_array :: proc(p: ^Parser, base: rawptr, elem: ^reflect.Type_Info, length: uintptr, user_unmarshallers: User_Unmarshaller_Map = nil) -> Unmarshal_Error { +unmarshal_array :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) { + assign_array :: proc(p: ^Parser, base: rawptr, elem: ^reflect.Type_Info, length: uintptr) -> Unmarshal_Error { unmarshal_expect_token(p, .Open_Bracket) for idx: uintptr = 0; p.curr_token.kind != .Close_Bracket; idx += 1 { @@ -683,7 +754,7 @@ unmarshal_array :: proc(p: ^Parser, v: any, user_unmarshallers: User_Unmarshalle elem_ptr := rawptr(uintptr(base) + idx*uintptr(elem.size)) elem := any{elem_ptr, elem.id} - unmarshal_value(p, elem, user_unmarshallers) or_return + unmarshal_value(p, elem) or_return if parse_comma(p) { break @@ -709,7 +780,7 @@ unmarshal_array :: proc(p: ^Parser, v: any, user_unmarshallers: User_Unmarshalle raw.data = raw_data(data) raw.len = int(length) - return assign_array(p, raw.data, t.elem, length, user_unmarshallers) + return assign_array(p, raw.data, t.elem, length) case reflect.Type_Info_Dynamic_Array: raw := (^mem.Raw_Dynamic_Array)(v.data) @@ -719,7 +790,7 @@ unmarshal_array :: proc(p: ^Parser, v: any, user_unmarshallers: User_Unmarshalle raw.cap = int(length) raw.allocator = p.allocator - return assign_array(p, raw.data, t.elem, length, user_unmarshallers) + return assign_array(p, raw.data, t.elem, length) case reflect.Type_Info_Array: // NOTE(bill): Allow lengths which are less than the dst array @@ -727,7 +798,7 @@ unmarshal_array :: proc(p: ^Parser, v: any, user_unmarshallers: User_Unmarshalle return UNSUPPORTED_TYPE } - return assign_array(p, v.data, t.elem, length, user_unmarshallers) + return assign_array(p, v.data, t.elem, length) case reflect.Type_Info_Enumerated_Array: // NOTE(bill): Allow lengths which are less than the dst array @@ -735,7 +806,7 @@ unmarshal_array :: proc(p: ^Parser, v: any, user_unmarshallers: User_Unmarshalle return UNSUPPORTED_TYPE } - return assign_array(p, v.data, t.elem, length, user_unmarshallers) + return assign_array(p, v.data, t.elem, length) case reflect.Type_Info_Complex: // NOTE(bill): Allow lengths which are less than the dst array @@ -744,9 +815,9 @@ unmarshal_array :: proc(p: ^Parser, v: any, user_unmarshallers: User_Unmarshalle } switch ti.id { - case complex32: return assign_array(p, v.data, type_info_of(f16), 2, user_unmarshallers) - case complex64: return assign_array(p, v.data, type_info_of(f32), 2, user_unmarshallers) - case complex128: return assign_array(p, v.data, type_info_of(f64), 2, user_unmarshallers) + case complex32: return assign_array(p, v.data, type_info_of(f16), 2) + case complex64: return assign_array(p, v.data, type_info_of(f32), 2) + case complex128: return assign_array(p, v.data, type_info_of(f64), 2) } return UNSUPPORTED_TYPE |