mirror of
https://github.com/odin-lang/Odin.git
synced 2026-04-19 04:50:29 +00:00
Merge pull request #5882 from IllusionMan1212/custom-json-marshalling
encoding/json: custom json (un)marshalling
This commit is contained in:
@@ -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}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user