Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON: Optionally write union variant tags and names #4293

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion core/encoding/json/marshal.odin
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ Marshal_Options :: struct {
indentation: int,
mjson_skipped_first_braces_start: bool,
mjson_skipped_first_braces_end: bool,

// Write union variant name and tag into data. Always creates a JSON object
// for each union value with a $data and $tag field, plus a $name field if
// the variant is a named type.
write_union_variant_info: bool,
}

marshal :: proc(v: any, opt: Marshal_Options = {}, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Marshal_Error) {
Expand Down Expand Up @@ -458,7 +463,42 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
tag -= 1
}
id := info.variants[tag].id
return marshal_to_writer(w, any{v.data, id}, opt)

if opt.write_union_variant_info {
opt_write_start(w, opt, '{') or_return
opt_write_iteration(w, opt, true) or_return

opt_write_key(w, opt, "$data") or_return
marshal_to_writer(w, any{v.data, id}, opt) or_return
opt_write_iteration(w, opt, false) or_return

/*
TODO: Somehow write the name of simple types like f32... But those
are hard to match when unmarshalling since they have Type_Info_Named

opt_write_key(w, opt, "$name") or_return

io.write_string(w, "\"") or_return
reflect.write_typeid(w, info.variants[tag].id) or_return
io.write_string(w, "\"") or_return*/

tin, tin_ok := info.variants[tag].variant.(runtime.Type_Info_Named)

if tin_ok {
opt_write_key(w, opt, "$name") or_return
io.write_string(w, "\"") or_return
io.write_string(w, tin.name)
io.write_string(w, "\"") or_return
opt_write_iteration(w, opt, false) or_return
}

opt_write_key(w, opt, "$tag") or_return
io.write_i64(w, tag)

opt_write_end(w, opt, '}') or_return
} else {
marshal_to_writer(w, any{v.data, id}, opt) or_return
}

case runtime.Type_Info_Enum:
if !opt.use_enum_names || len(info.names) == 0 {
Expand Down
91 changes: 91 additions & 0 deletions core/encoding/json/parser.odin
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,97 @@ parse_value :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err:
return
}

skip_value :: proc(p: ^Parser) -> (err: Error) {
err = .None
token := p.curr_token
#partial switch token.kind {
case .Null:
advance_token(p)
return
case .False:
advance_token(p)
return
case .True:
advance_token(p)
return

case .Integer:
advance_token(p)
return
case .Float:
advance_token(p)
return

case .Ident:
if p.spec == .MJSON {
advance_token(p)
return
}

case .String:
advance_token(p)
return

case .Open_Brace:
num_braces := 1

for num_braces > 0 {
advance_token(p)

ct := p.curr_token

if ct.kind == .Invalid || ct.kind == .EOF {
break
}

if ct.kind == .Open_Brace {
num_braces += 1
} else if ct.kind == .Close_Brace {
num_braces -= 1
}
}

advance_token(p)
return

case .Open_Bracket:
num_brackets := 1

for num_brackets > 0 {
advance_token(p)

ct := p.curr_token

if ct.kind == .Invalid || ct.kind == .EOF {
break
}

if ct.kind == .Open_Bracket {
num_brackets += 1
} else if ct.kind == .Close_Bracket {
num_brackets -= 1
}
}

advance_token(p)
return

case:
if p.spec != .JSON {
switch {
case allow_token(p, .Infinity):
return
case allow_token(p, .NaN):
return
}
}
}

err = .Unexpected_Token
advance_token(p)
return
}

parse_array :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) {
err = .None
expect_token(p, .Open_Bracket) or_return
Expand Down
97 changes: 97 additions & 0 deletions core/encoding/json/unmarshal.odin
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,103 @@ unmarshal_value :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) {
assign_int(tag, 1)
}
} else if v.id != Value {
// Check for $data, $name and $tag: They are written if the JSON
// was marashalled using write_union_variant_info set to true.
if token.kind == .Open_Brace {
variant_name: string
variant_tag: i64 = -1
has_data_parser: bool
data_parser: Parser

check_p := p^
check_p.parse_integers = true
unmarshal_expect_token(&check_p, .Open_Brace)

parse_key :: proc(p: ^Parser) -> (key: string, err: Error) {
tok := p.curr_token
if p.spec != .JSON {
if allow_token(p, .Ident) {
return tok.text, nil
}
}
if tok_err := expect_token(p, .String); tok_err != nil {
err = .Expected_String_For_Object_Key
return
}
return tok.text[1:len(tok.text)-1], nil
}

for check_p.curr_token.kind != .Close_Brace {
key := parse_key(&check_p) or_return

expect_token(&check_p, .Colon) or_return

if key == "$data" {
has_data_parser = true
data_parser = check_p
skip_value(&check_p) or_return
} else if key == "$name" {
expect_token(&check_p, .String) or_return
variant_name = check_p.prev_token.text[1:len(check_p.prev_token.text)-1]
} else if key == "$tag" {
expect_token(&check_p, .Integer) or_return
if i, i_ok := strconv.parse_i64(check_p.prev_token.text); i_ok {
variant_tag = i
}
} else {
skip_value(&check_p) or_return
}

if parse_comma(&check_p) {
break
}
}

unmarshal_expect_token(&check_p, .Close_Brace)

if has_data_parser {
if variant_name != "" {
for variant, i in u.variants {
named := variant.variant.(runtime.Type_Info_Named) or_continue

if named.name != variant_name {
continue
}

variant_any := any{v.data, variant.id}
if err = unmarshal_value(&data_parser, variant_any); err == nil {
p^ = data_parser

raw_tag := i
if !u.no_nil { raw_tag += 1 }
tag := any{rawptr(uintptr(v.data) + u.tag_offset), u.tag_type.id}
assign_int(tag, raw_tag)
return
}
}
}

if variant_tag != -1 {
for variant, i in u.variants {
if i64(i) != variant_tag {
continue
}

variant_any := any{v.data, variant.id}
if err = unmarshal_value(&data_parser, variant_any); err == nil {
p^ = data_parser

raw_tag := i
if !u.no_nil { raw_tag += 1 }
tag := any{rawptr(uintptr(v.data) + u.tag_offset), u.tag_type.id}
assign_int(tag, raw_tag)
return
}
}
}
}
}

for variant, i in u.variants {
variant_any := any{v.data, variant.id}
variant_p := p^
Expand Down