diff options
| author | Jeroen van Rijn <Kelimion@users.noreply.github.com> | 2026-01-01 16:07:01 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-01 16:07:01 +0000 |
| commit | b6ea6f7a12d6f6f10cd9400ec7fde9b9ce91612e (patch) | |
| tree | f042d0d7658c4b7d86fc36daae2aeb01a7824ba0 /core | |
| parent | b446b91251af00d4d648b5e178bc4f4e7a735bb9 (diff) | |
| parent | 83fb7ba9523bc67b9f3d72ba961abd3a55dffda0 (diff) | |
Merge pull request #5882 from IllusionMan1212/custom-json-marshalling
encoding/json: custom json (un)marshalling
Diffstat (limited to 'core')
| -rw-r--r-- | core/encoding/json/marshal.odin | 79 | ||||
| -rw-r--r-- | core/encoding/json/unmarshal.odin | 88 |
2 files changed, 164 insertions, 3 deletions
diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index c4e348aa8..011fc6f91 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -62,6 +62,78 @@ Marshal_Options :: struct { mjson_skipped_first_braces_end: bool, } +User_Marshaler :: #type proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> 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 { @@ -91,6 +163,13 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: return } + if _user_marshalers != nil { + marshaler := _user_marshalers[v.id] + if marshaler != nil { + return marshaler(w, v, opt) + } + } + ti := runtime.type_info_base(type_info_of(v.id)) a := any{v.data, ti.id} diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index 58365b684..2aa3c0913 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -26,6 +26,80 @@ Unmarshal_Error :: union { Unsupported_Type_Error, } +User_Unmarshaler :: #type proc(p: ^Parser, v: any) -> 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 { @@ -37,8 +111,10 @@ unmarshal_any :: proc(data: []byte, v: any, spec := DEFAULT_SPECIFICATION, alloc 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) @@ -274,12 +350,18 @@ unmarshal_string_token :: proc(p: ^Parser, val: any, token: Token, ti: ^reflect. return false, nil } - @(private) 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_unmarshalers != nil { + unmarshaler := _user_unmarshalers[v.id] + if unmarshaler != nil { + return unmarshaler(p, v) + } + } + v := v ti := reflect.type_info_base(type_info_of(v.id)) if u, ok := ti.variant.(reflect.Type_Info_Union); ok && token.kind != .Null { |