diff options
Diffstat (limited to 'core/encoding/json')
| -rw-r--r-- | core/encoding/json/marshal.odin | 132 | ||||
| -rw-r--r-- | core/encoding/json/types.odin | 27 |
2 files changed, 121 insertions, 38 deletions
diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index 85eca50b6..6922f9b77 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -7,6 +7,7 @@ import "core:strconv" import "core:strings" import "core:reflect" import "core:io" +import "core:slice" Marshal_Data_Error :: enum { None, @@ -18,29 +19,40 @@ Marshal_Error :: union #shared_nil { io.Error, } -// careful with MJSON maps & non quotes usage as keys without whitespace will lead to bad results +// careful with MJSON maps & non quotes usage as keys with whitespace will lead to bad results Marshal_Options :: struct { // output based on spec spec: Specification, - // use line breaks & tab|spaces + // Use line breaks & tabs/spaces pretty: bool, - // spacing + // Use spaces for indentation instead of tabs use_spaces: bool, - spaces: int, - // state - indentation: int, + // Given use_spaces true, use this many spaces per indent level. 0 means 4 spaces. + spaces: int, - // option to output uint in JSON5 & MJSON + // Output uint as hex in JSON5 & MJSON write_uint_as_hex: bool, - // mjson output options + // If spec is MJSON and this is true, then keys will be quoted. + // + // WARNING: If your keys contain whitespace and this is false, then the + // output will be bad. mjson_keys_use_quotes: bool, + + // If spec is MJSON and this is true, then use '=' as delimiter between + // keys and values, otherwise ':' is used. mjson_keys_use_equal_sign: bool, - // mjson state + // When outputting a map, sort the output by key. + // + // NOTE: This will temp allocate and sort a list for each map. + sort_maps_by_key: bool, + + // Internal state + indentation: int, mjson_skipped_first_braces_start: bool, mjson_skipped_first_braces_end: bool, } @@ -263,36 +275,81 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: map_cap := uintptr(runtime.map_cap(m^)) ks, vs, hs, _, _ := runtime.map_kvh_data_dynamic(m^, info.map_info) - i := 0 - for bucket_index in 0..<map_cap { - runtime.map_hash_is_valid(hs[bucket_index]) or_continue - - opt_write_iteration(w, opt, i) or_return - i += 1 + if opt.sort_maps_by_key { + Entry :: struct { + key: string, + value: any, + } - key := rawptr(runtime.map_cell_index_dynamic(ks, info.map_info.ks, bucket_index)) - value := rawptr(runtime.map_cell_index_dynamic(vs, info.map_info.vs, bucket_index)) + // If we are sorting the map by key, then we temp alloc an array + // and sort it, then output the result. + sorted := make([dynamic]Entry, 0, map_cap, context.temp_allocator) + for bucket_index in 0..<map_cap { + runtime.map_hash_is_valid(hs[bucket_index]) or_continue - // check for string type - { - v := any{key, info.key.id} - ti := runtime.type_info_base(type_info_of(v.id)) - a := any{v.data, ti.id} + key := rawptr(runtime.map_cell_index_dynamic(ks, info.map_info.ks, bucket_index)) + value := rawptr(runtime.map_cell_index_dynamic(vs, info.map_info.vs, bucket_index)) name: string - #partial switch info in ti.variant { - case runtime.Type_Info_String: - switch s in a { - case string: name = s - case cstring: name = string(s) - } - opt_write_key(w, opt, name) or_return + // check for string type + { + v := any{key, info.key.id} + ti := runtime.type_info_base(type_info_of(v.id)) + a := any{v.data, ti.id} + + #partial switch info in ti.variant { + case runtime.Type_Info_String: + switch s in a { + case string: name = s + case cstring: name = string(s) + } - case: return .Unsupported_Type + case: return .Unsupported_Type + } } + + append(&sorted, Entry { key = name, value = any{value, info.value.id}}) } - marshal_to_writer(w, any{value, info.value.id}, opt) or_return + slice.sort_by(sorted[:], proc(i, j: Entry) -> bool { return i.key < j.key }) + + for s, i in sorted { + opt_write_iteration(w, opt, i) or_return + opt_write_key(w, opt, s.key) or_return + marshal_to_writer(w, s.value, opt) or_return + } + } else { + i := 0 + for bucket_index in 0..<map_cap { + runtime.map_hash_is_valid(hs[bucket_index]) or_continue + + opt_write_iteration(w, opt, i) or_return + i += 1 + + key := rawptr(runtime.map_cell_index_dynamic(ks, info.map_info.ks, bucket_index)) + value := rawptr(runtime.map_cell_index_dynamic(vs, info.map_info.vs, bucket_index)) + + // check for string type + { + v := any{key, info.key.id} + ti := runtime.type_info_base(type_info_of(v.id)) + a := any{v.data, ti.id} + name: string + + #partial switch info in ti.variant { + case runtime.Type_Info_String: + switch s in a { + case string: name = s + case cstring: name = string(s) + } + opt_write_key(w, opt, name) or_return + + case: return .Unsupported_Type + } + } + + marshal_to_writer(w, any{value, info.value.id}, opt) or_return + } } } @@ -424,8 +481,9 @@ opt_write_key :: proc(w: io.Writer, opt: ^Marshal_Options, name: string) -> (err // insert start byte and increase indentation on pretty opt_write_start :: proc(w: io.Writer, opt: ^Marshal_Options, c: byte) -> (err: io.Error) { - // skip mjson starting braces - if opt.spec == .MJSON && !opt.mjson_skipped_first_braces_start { + // Skip MJSON starting braces. We make sure to only do this for c == '{', + // skipping a starting '[' is not allowed. + if opt.spec == .MJSON && !opt.mjson_skipped_first_braces_start && opt.indentation == 0 && c == '{' { opt.mjson_skipped_first_braces_start = true return } @@ -473,11 +531,9 @@ opt_write_iteration :: proc(w: io.Writer, opt: ^Marshal_Options, iteration: int) // decrease indent, write spacing and insert end byte opt_write_end :: proc(w: io.Writer, opt: ^Marshal_Options, c: byte) -> (err: io.Error) { - if opt.spec == .MJSON && opt.mjson_skipped_first_braces_start && !opt.mjson_skipped_first_braces_end { - if opt.indentation == 0 { - opt.mjson_skipped_first_braces_end = true - return - } + if opt.spec == .MJSON && opt.mjson_skipped_first_braces_start && !opt.mjson_skipped_first_braces_end && opt.indentation == 0 && c == '}' { + opt.mjson_skipped_first_braces_end = true + return } opt.indentation -= 1 diff --git a/core/encoding/json/types.odin b/core/encoding/json/types.odin index 089fd9c9b..60b3defa1 100644 --- a/core/encoding/json/types.odin +++ b/core/encoding/json/types.odin @@ -1,5 +1,7 @@ package json +import "core:strings" + /* JSON strict JSON @@ -104,4 +106,29 @@ destroy_value :: proc(value: Value, allocator := context.allocator) { case String: delete(v) } +} + +clone_value :: proc(value: Value, allocator := context.allocator) -> Value { + context.allocator = allocator + + #partial switch &v in value { + case Object: + new_o := make(Object, len(v)) + for key, elem in v { + new_o[strings.clone(key)] = clone_value(elem) + } + return new_o + case Array: + len := len(v) + new_a := make(Array, len) + vv := v + for elem, idx in vv { + new_a[idx] = clone_value(elem) + } + return new_a + case String: + return strings.clone(v) + } + + return value }
\ No newline at end of file |