mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-02 20:58:15 +00:00
JSON: Option to sort marshaled maps before outputting. Also added a json.clone_value proc
This commit is contained in:
+100
-44
@@ -7,6 +7,7 @@ import "core:strconv"
|
|||||||
import "core:strings"
|
import "core:strings"
|
||||||
import "core:reflect"
|
import "core:reflect"
|
||||||
import "core:io"
|
import "core:io"
|
||||||
|
import "core:slice"
|
||||||
|
|
||||||
Marshal_Data_Error :: enum {
|
Marshal_Data_Error :: enum {
|
||||||
None,
|
None,
|
||||||
@@ -18,29 +19,40 @@ Marshal_Error :: union #shared_nil {
|
|||||||
io.Error,
|
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 {
|
Marshal_Options :: struct {
|
||||||
// output based on spec
|
// output based on spec
|
||||||
spec: Specification,
|
spec: Specification,
|
||||||
|
|
||||||
// use line breaks & tab|spaces
|
// Use line breaks & tabs/spaces
|
||||||
pretty: bool,
|
pretty: bool,
|
||||||
|
|
||||||
// spacing
|
// Use spaces for indentation instead of tabs
|
||||||
use_spaces: bool,
|
use_spaces: bool,
|
||||||
|
|
||||||
|
// Given use_spaces true, use this many spaces per indent level. 0 means 4 spaces.
|
||||||
spaces: int,
|
spaces: int,
|
||||||
|
|
||||||
// state
|
// Output uint as hex in JSON5 & MJSON
|
||||||
indentation: int,
|
|
||||||
|
|
||||||
// option to output uint in JSON5 & MJSON
|
|
||||||
write_uint_as_hex: bool,
|
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,
|
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_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_start: bool,
|
||||||
mjson_skipped_first_braces_end: 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^))
|
map_cap := uintptr(runtime.map_cap(m^))
|
||||||
ks, vs, hs, _, _ := runtime.map_kvh_data_dynamic(m^, info.map_info)
|
ks, vs, hs, _, _ := runtime.map_kvh_data_dynamic(m^, info.map_info)
|
||||||
|
|
||||||
i := 0
|
if opt.sort_maps_by_key {
|
||||||
for bucket_index in 0..<map_cap {
|
Entry :: struct {
|
||||||
runtime.map_hash_is_valid(hs[bucket_index]) or_continue
|
key: string,
|
||||||
|
value: any,
|
||||||
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
|
// 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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
append(&sorted, Entry { key = name, value = any{value, info.value.id}})
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// insert start byte and increase indentation on pretty
|
||||||
opt_write_start :: proc(w: io.Writer, opt: ^Marshal_Options, c: byte) -> (err: io.Error) {
|
opt_write_start :: proc(w: io.Writer, opt: ^Marshal_Options, c: byte) -> (err: io.Error) {
|
||||||
// skip mjson starting braces
|
// Skip MJSON starting braces. We make sure to only do this for c == '{',
|
||||||
if opt.spec == .MJSON && !opt.mjson_skipped_first_braces_start {
|
// 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
|
opt.mjson_skipped_first_braces_start = true
|
||||||
return
|
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
|
// decrease indent, write spacing and insert end byte
|
||||||
opt_write_end :: proc(w: io.Writer, opt: ^Marshal_Options, c: byte) -> (err: io.Error) {
|
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.spec == .MJSON && opt.mjson_skipped_first_braces_start && !opt.mjson_skipped_first_braces_end && opt.indentation == 0 && c == '}' {
|
||||||
if opt.indentation == 0 {
|
opt.mjson_skipped_first_braces_end = true
|
||||||
opt.mjson_skipped_first_braces_end = true
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
opt.indentation -= 1
|
opt.indentation -= 1
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package json
|
package json
|
||||||
|
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
JSON
|
JSON
|
||||||
strict JSON
|
strict JSON
|
||||||
@@ -105,3 +107,28 @@ destroy_value :: proc(value: Value, allocator := context.allocator) {
|
|||||||
delete(v)
|
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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user