diff --git a/core/encoding/json/unparse.odin b/core/encoding/json/unparse.odin new file mode 100644 index 000000000..bff85c0da --- /dev/null +++ b/core/encoding/json/unparse.odin @@ -0,0 +1,89 @@ +package encoding_json + +import "base:runtime" +import "core:strings" +import "core:io" +import "core:slice" + +Unparse_Error :: union #shared_nil { + io.Error, + runtime.Allocator_Error, +} + +@(require_results) +unparse :: proc(v: Value, opt: Marshal_Options = {}, allocator := context.allocator, loc := #caller_location) -> (data: string, err: Unparse_Error) { + b := strings.builder_make(allocator, loc) + defer if err != nil { + strings.builder_destroy(&b) + } + + // temp guard in case we are sorting map keys, which will use temp allocations + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator) + + opt := opt + unparse_to_builder(&b, v, &opt) or_return + data = string(b.buf[:]) + return +} + +@(require_results) +unparse_to_builder :: proc(b: ^strings.Builder, v: Value, opt: ^Marshal_Options) -> Unparse_Error { + return unparse_to_writer(strings.to_writer(b), v, opt) +} + +@(require_results) +unparse_to_writer :: proc(w: io.Writer, value: Value, opt: ^Marshal_Options) -> Unparse_Error { + switch v in value { + case nil, Null: + io.write_string(w, "null") or_return + case Integer: + base := 16 if opt.write_uint_as_hex && (opt.spec == .JSON5 || opt.spec == .MJSON) else 10 + io.write_i64(w, v, base) or_return + case Float: + io.write_f64(w, v) or_return + case Boolean: + io.write_string(w, "true" if v else "false") or_return + case String: + io.write_quoted_string(w, v, '"', nil, true) or_return + case Array: + opt_write_start(w, opt, '[') or_return + for e, i in v { + opt_write_iteration(w, opt, i == 0) or_return + unparse_to_writer (w, e, opt) or_return + } + opt_write_end(w, opt, ']') or_return + case Object: + if !opt.sort_maps_by_key { + opt_write_start(w, opt, '{') or_return + for first_iteration := true; key, val in v { + opt_write_iteration(w, opt, first_iteration) or_return + opt_write_key (w, opt, key) or_return + unparse_to_writer (w, val, opt) or_return + first_iteration = false + } + opt_write_end(w, opt, '}') or_return + } else { + Map_Entry :: struct { + key: string, + value: Value, + } + + entries := make([dynamic]Map_Entry, 0, len(v), context.temp_allocator) or_return + for key, val in v { + _, _ = append(&entries, Map_Entry{key, val}) + } + + slice.sort_by(entries[:], proc(i, j: Map_Entry) -> bool { return i.key < j.key }) + + opt_write_start(w, opt, '{') or_return + for e, i in entries { + opt_write_iteration(w, opt, i == 0) or_return + opt_write_key (w, opt, e.key) or_return + unparse_to_writer (w, e.value, opt) or_return + } + opt_write_end(w, opt, '}') or_return + } + return nil + } + return nil +}