marshal output options with pretty option and other config

This commit is contained in:
Michael Kutowski
2022-08-05 18:53:29 +02:00
committed by GitHub
parent c0d2359a91
commit a6fa41e290
+215 -52
View File
@@ -1,41 +1,59 @@
package json package src
import "core:mem"
import "core:math/bits" import "core:math/bits"
import "core:mem"
import "core:runtime" import "core:runtime"
import "core:strconv" import "core:strconv"
import "core:strings"
import "core:io" import "core:io"
import "core:strings"
import "core:encoding/json"
Marshal_Data_Error :: enum { // supports json specs
None, Marshal_Options :: struct {
Unsupported_Type, // output based on spec
spec: json.Specification,
// use line breaks & tab|spaces
pretty: bool,
// spacing
use_spaces: bool,
spaces: int,
tabs: int,
// state
indentation: int,
// mjson output options
mjson_keys_use_quotes: bool,
mjson_keys_use_equal_sign: bool,
// mjson state
mjson_skipped_first_braces_start: bool,
mjson_skipped_first_braces_end: bool,
} }
Marshal_Error :: union #shared_nil { marshal :: proc(v: any, opt: Marshal_Options = {}, allocator := context.allocator) -> (data: []byte, err: json.Marshal_Error) {
Marshal_Data_Error, b := strings.builder_make(allocator)
io.Error,
}
marshal :: proc(v: any, allocator := context.allocator) -> (data: []byte, err: Marshal_Error) {
b := strings.make_builder(allocator)
defer if err != nil { defer if err != nil {
strings.destroy_builder(&b) strings.builder_destroy(&b)
} }
marshal_to_builder(&b, v) or_return opt := opt
marshal_to_builder(&b, v, &opt) or_return
if len(b.buf) != 0 { if len(b.buf) != 0 {
data = b.buf[:] data = b.buf[:]
} }
return data, nil return data, nil
} }
marshal_to_builder :: proc(b: ^strings.Builder, v: any) -> Marshal_Error { marshal_to_builder :: proc(b: ^strings.Builder, v: any, opt: ^Marshal_Options) -> json.Marshal_Error {
return marshal_to_writer(strings.to_writer(b), v) return marshal_to_writer(strings.to_writer(b), v, opt)
} }
marshal_to_writer :: proc(w: io.Writer, v: any) -> (err: Marshal_Error) { marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: json.Marshal_Error) {
if v == nil { if v == nil {
io.write_string(w, "null") or_return io.write_string(w, "null") or_return
return return
@@ -166,52 +184,48 @@ marshal_to_writer :: proc(w: io.Writer, v: any) -> (err: Marshal_Error) {
return .Unsupported_Type return .Unsupported_Type
case runtime.Type_Info_Array: case runtime.Type_Info_Array:
io.write_byte(w, '[') or_return opt_write_start(w, opt, '[') or_return
for i in 0..<info.count { for i in 0..<info.count {
if i > 0 { io.write_string(w, ", ") or_return } opt_write_iteration(w, opt, i) or_return
data := uintptr(v.data) + uintptr(i*info.elem_size) data := uintptr(v.data) + uintptr(i*info.elem_size)
marshal_to_writer(w, any{rawptr(data), info.elem.id}) or_return marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
} }
io.write_byte(w, ']') or_return opt_write_end(w, opt, ']') or_return
case runtime.Type_Info_Enumerated_Array: case runtime.Type_Info_Enumerated_Array:
index := runtime.type_info_base(info.index).variant.(runtime.Type_Info_Enum) index := runtime.type_info_base(info.index).variant.(runtime.Type_Info_Enum)
io.write_byte(w, '[') or_return opt_write_start(w, opt, '[') or_return
for i in 0..<info.count { for i in 0..<info.count {
if i > 0 { io.write_string(w, ", ") or_return } opt_write_iteration(w, opt, i) or_return
data := uintptr(v.data) + uintptr(i*info.elem_size) data := uintptr(v.data) + uintptr(i*info.elem_size)
marshal_to_writer(w, any{rawptr(data), info.elem.id}) or_return marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
} }
io.write_byte(w, ']') or_return opt_write_end(w, opt, ']') or_return
case runtime.Type_Info_Dynamic_Array: case runtime.Type_Info_Dynamic_Array:
io.write_byte(w, '[') or_return opt_write_start(w, opt, '[') or_return
array := cast(^mem.Raw_Dynamic_Array)v.data array := cast(^mem.Raw_Dynamic_Array)v.data
for i in 0..<array.len { for i in 0..<array.len {
if i > 0 { io.write_string(w, ", ") or_return } opt_write_iteration(w, opt, i) or_return
data := uintptr(array.data) + uintptr(i*info.elem_size) data := uintptr(array.data) + uintptr(i*info.elem_size)
marshal_to_writer(w, any{rawptr(data), info.elem.id}) or_return marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
} }
io.write_byte(w, ']') or_return opt_write_end(w, opt, ']') or_return
case runtime.Type_Info_Slice: case runtime.Type_Info_Slice:
io.write_byte(w, '[') or_return opt_write_start(w, opt, '[') or_return
slice := cast(^mem.Raw_Slice)v.data slice := cast(^mem.Raw_Slice)v.data
for i in 0..<slice.len { for i in 0..<slice.len {
if i > 0 { io.write_string(w, ", ") or_return } opt_write_iteration(w, opt, i) or_return
data := uintptr(slice.data) + uintptr(i*info.elem_size) data := uintptr(slice.data) + uintptr(i*info.elem_size)
marshal_to_writer(w, any{rawptr(data), info.elem.id}) or_return marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
} }
io.write_byte(w, ']') or_return opt_write_end(w, opt, ']') or_return
case runtime.Type_Info_Map: case runtime.Type_Info_Map:
m := (^mem.Raw_Map)(v.data) m := (^mem.Raw_Map)(v.data)
opt_write_start(w, opt, '{') or_return
io.write_byte(w, '{') or_return
if m != nil { if m != nil {
if info.generated_struct == nil { if info.generated_struct == nil {
return .Unsupported_Type return .Unsupported_Type
@@ -223,31 +237,62 @@ marshal_to_writer :: proc(w: io.Writer, v: any) -> (err: Marshal_Error) {
entry_size := ed.elem_size entry_size := ed.elem_size
for i in 0..<entries.len { for i in 0..<entries.len {
if i > 0 { io.write_string(w, ", ") or_return } opt_write_iteration(w, opt, i) or_return
data := uintptr(entries.data) + uintptr(i*entry_size) data := uintptr(entries.data) + uintptr(i*entry_size)
key := rawptr(data + entry_type.offsets[2]) key := rawptr(data + entry_type.offsets[2])
value := rawptr(data + entry_type.offsets[3]) value := rawptr(data + entry_type.offsets[3])
marshal_to_writer(w, any{key, info.key.id}) or_return // check for string type
io.write_string(w, ": ") or_return {
marshal_to_writer(w, any{value, info.value.id}) or_return 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: {
// fmt.eprintln("WAS STRING")
switch s in a {
case string: name = s
case cstring: name = string(s)
}
// NOTE need to ensure that map keys are valid for mjson and contain no whitespace
if opt.spec == .MJSON && !opt.mjson_keys_use_quotes {
name, _ = strings.replace_all(name, " ", "_", context.temp_allocator)
}
opt_write_key(w, opt, name) or_return
}
case: {
// TODO better error output?
return .Unsupported_Type
}
}
}
marshal_to_writer(w, any{value, info.value.id}, opt) or_return
} }
} }
io.write_byte(w, '}') or_return
opt_write_end(w, opt, '}') or_return
case runtime.Type_Info_Struct: case runtime.Type_Info_Struct:
io.write_byte(w, '{') or_return opt_write_start(w, opt, '{') or_return
for name, i in info.names { for name, i in info.names {
if i > 0 { io.write_string(w, ", ") or_return } opt_write_iteration(w, opt, i) or_return
io.write_quoted_string(w, name) or_return opt_write_key(w, opt, name) or_return
io.write_string(w, ": ") or_return
id := info.types[i].id id := info.types[i].id
data := rawptr(uintptr(v.data) + info.offsets[i]) data := rawptr(uintptr(v.data) + info.offsets[i])
marshal_to_writer(w, any{data, id}) or_return marshal_to_writer(w, any{data, id}, opt) or_return
} }
io.write_byte(w, '}') or_return
opt_write_end(w, opt, '}') or_return
case runtime.Type_Info_Union: case runtime.Type_Info_Union:
tag_ptr := uintptr(v.data) + info.tag_offset tag_ptr := uintptr(v.data) + info.tag_offset
@@ -270,11 +315,11 @@ marshal_to_writer :: proc(w: io.Writer, v: any) -> (err: Marshal_Error) {
io.write_string(w, "null") or_return io.write_string(w, "null") or_return
} else { } else {
id := info.variants[tag-1].id id := info.variants[tag-1].id
return marshal_to_writer(w, any{v.data, id}) return marshal_to_writer(w, any{v.data, id}, opt)
} }
case runtime.Type_Info_Enum: case runtime.Type_Info_Enum:
return marshal_to_writer(w, any{v.data, info.base.id}) return marshal_to_writer(w, any{v.data, info.base.id}, opt)
case runtime.Type_Info_Bit_Set: case runtime.Type_Info_Bit_Set:
is_bit_set_different_endian_to_platform :: proc(ti: ^runtime.Type_Info) -> bool { is_bit_set_different_endian_to_platform :: proc(ti: ^runtime.Type_Info) -> bool {
@@ -330,3 +375,121 @@ marshal_to_writer :: proc(w: io.Writer, v: any) -> (err: Marshal_Error) {
return return
} }
// write key as quoted string or with optional quotes in mjson
opt_write_key :: proc(w: io.Writer, opt: ^Marshal_Options, name: string) -> (err: io.Error) {
switch opt.spec {
case .JSON, .JSON5: {
io.write_quoted_string(w, name) or_return
io.write_string(w, ": ") or_return
}
case .MJSON: {
if opt.mjson_keys_use_quotes {
io.write_quoted_string(w, name) or_return
} else {
io.write_string(w, name) or_return
}
if opt.mjson_keys_use_equal_sign {
io.write_string(w, " = ") or_return
} else {
io.write_string(w, ": ") or_return
}
}
}
return
}
// 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 {
opt.mjson_skipped_first_braces_start = true
return
}
io.write_byte(w, c) or_return
opt.indentation += 1
if opt.pretty {
io.write_byte(w, '\n') or_return
}
return
}
// insert comma seperation and write indentations
opt_write_iteration :: proc(w: io.Writer, opt: ^Marshal_Options, iteration: int) -> (err: io.Error) {
switch opt.spec {
case .JSON, .JSON5: {
if iteration > 0 {
io.write_string(w, ", ") or_return
if opt.pretty {
io.write_byte(w, '\n') or_return
}
}
opt_write_indentation(w, opt) or_return
}
case .MJSON: {
if iteration > 0 {
// on pretty no commas necessary
if opt.pretty {
io.write_byte(w, '\n') or_return
} else {
// NOTE comma seperation necessary for non pretty output!
io.write_string(w, ", ") or_return
}
}
opt_write_indentation(w, opt) or_return
}
}
return
}
// 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
}
}
opt.indentation -= 1
if opt.pretty {
io.write_byte(w, '\n') or_return
opt_write_indentation(w, opt) or_return
}
io.write_byte(w, c) or_return
return
}
// writes current indentation level based on options
opt_write_indentation :: proc(w: io.Writer, opt: ^Marshal_Options) -> (err: io.Error) {
if !opt.pretty {
return
}
// TODO optimize?
if opt.use_spaces {
// NOTE maybe max(1, opt.spaces)
for _ in 0..<opt.indentation * opt.spaces {
io.write_byte(w, ' ') or_return
}
} else {
for _ in 0..<opt.indentation {
io.write_byte(w, '\t') or_return
}
}
return
}