From dbdad0476d1b9dd72eddd6b8584beca855585b13 Mon Sep 17 00:00:00 2001 From: VladPavliuk Date: Sat, 13 Jul 2024 00:07:48 +0300 Subject: [PATCH 1/3] Allow to `marshal` and `unmarshal` maps with int keys --- core/encoding/json/marshal.odin | 78 +++++++++++--------- core/encoding/json/unmarshal.odin | 30 ++++++-- tests/core/encoding/json/test_core_json.odin | 40 ++++++++++ 3 files changed, 107 insertions(+), 41 deletions(-) diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index 0464c24d1..31b076e95 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -100,38 +100,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: case runtime.Type_Info_Integer: buf: [40]byte - u: u128 - switch i in a { - case i8: u = u128(i) - case i16: u = u128(i) - case i32: u = u128(i) - case i64: u = u128(i) - case i128: u = u128(i) - case int: u = u128(i) - case u8: u = u128(i) - case u16: u = u128(i) - case u32: u = u128(i) - case u64: u = u128(i) - case u128: u = u128(i) - case uint: u = u128(i) - case uintptr: u = u128(i) - - case i16le: u = u128(i) - case i32le: u = u128(i) - case i64le: u = u128(i) - case u16le: u = u128(i) - case u32le: u = u128(i) - case u64le: u = u128(i) - case u128le: u = u128(i) - - case i16be: u = u128(i) - case i32be: u = u128(i) - case i64be: u = u128(i) - case u16be: u = u128(i) - case u32be: u = u128(i) - case u64be: u = u128(i) - case u128be: u = u128(i) - } + u := cast_any_int_to_u128(a) s: string @@ -310,7 +279,12 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: case cstring: name = string(s) } opt_write_key(w, opt, name) or_return - + case runtime.Type_Info_Integer: + buf: [40]byte + u := cast_any_int_to_u128(ka) + name = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*kti.size, "0123456789", nil) + + opt_write_key(w, opt, name) or_return case: return .Unsupported_Type } } @@ -657,3 +631,41 @@ opt_write_indentation :: proc(w: io.Writer, opt: ^Marshal_Options) -> (err: io.E return } + +@(private) +cast_any_int_to_u128 :: proc(any_int_value: any) -> u128 { + u: u128 = 0 + switch i in any_int_value { + case i8: u = u128(i) + case i16: u = u128(i) + case i32: u = u128(i) + case i64: u = u128(i) + case i128: u = u128(i) + case int: u = u128(i) + case u8: u = u128(i) + case u16: u = u128(i) + case u32: u = u128(i) + case u64: u = u128(i) + case u128: u = u128(i) + case uint: u = u128(i) + case uintptr: u = u128(i) + + case i16le: u = u128(i) + case i32le: u = u128(i) + case i64le: u = u128(i) + case u16le: u = u128(i) + case u32le: u = u128(i) + case u64le: u = u128(i) + case u128le: u = u128(i) + + case i16be: u = u128(i) + case i32be: u = u128(i) + case i64be: u = u128(i) + case u16be: u = u128(i) + case u32be: u = u128(i) + case u64be: u = u128(i) + case u128be: u = u128(i) + } + + return u +} \ No newline at end of file diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index eb59e7838..2a4678719 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -475,7 +475,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm } case reflect.Type_Info_Map: - if !reflect.is_string(t.key) { + if !reflect.is_string(t.key) && !reflect.is_integer(t.key) { return UNSUPPORTED_TYPE } raw_map := (^mem.Raw_Map)(v.data) @@ -492,25 +492,39 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm key, _ := parse_object_key(p, p.allocator) unmarshal_expect_token(p, .Colon) - + mem.zero_slice(elem_backing) if uerr := unmarshal_value(p, map_backing_value); uerr != nil { delete(key, p.allocator) return uerr } - key_ptr := rawptr(&key) + key_ptr: rawptr - key_cstr: cstring - if reflect.is_cstring(t.key) { - key_cstr = cstring(raw_data(key)) - key_ptr = &key_cstr + #partial switch tk in t.key.variant { + case runtime.Type_Info_String: + key_ptr = rawptr(&key) + key_cstr: cstring + if reflect.is_cstring(t.key) { + key_cstr = cstring(raw_data(key)) + key_ptr = &key_cstr + } + case runtime.Type_Info_Integer: + i, ok := strconv.parse_i128(key) + if !ok { return UNSUPPORTED_TYPE } + key_ptr = rawptr(&i) + case: return UNSUPPORTED_TYPE } - + set_ptr := runtime.__dynamic_map_set_without_hash(raw_map, t.map_info, key_ptr, map_backing_value.data) if set_ptr == nil { delete(key, p.allocator) } + + // there's no need to keep string value on the heap, since it was copied into map + if reflect.is_integer(t.key) { + delete(key, p.allocator) + } if parse_comma(p) { break map_loop diff --git a/tests/core/encoding/json/test_core_json.odin b/tests/core/encoding/json/test_core_json.odin index 92c050952..dfc655af0 100644 --- a/tests/core/encoding/json/test_core_json.odin +++ b/tests/core/encoding/json/test_core_json.odin @@ -3,6 +3,10 @@ package test_core_json import "core:encoding/json" import "core:testing" import "core:mem/virtual" +import "core:fmt" +import "base:runtime" +import "core:log" +import "core:strings" @test parse_json :: proc(t: ^testing.T) { @@ -368,4 +372,40 @@ utf8_string_of_multibyte_characters :: proc(t: ^testing.T) { val, err := json.parse_string(`"🐛✅"`) defer json.destroy_value(val) testing.expectf(t, err == nil, "Expected `json.parse` to return nil, got %v", err) +} + +@test +map_with_integer_keys :: proc(t: ^testing.T) { + my_map := make(map[i32]string) + defer delete_map(my_map) + + my_map[-1] = "a" + my_map[0] = "b" + my_map[42] = "c" + my_map[99999999] = "d" + + marshaled_data, marshal_err := json.marshal(my_map) + defer delete(marshaled_data) + + testing.expectf(t, marshal_err == nil, "Expected `json.marshal` to return nil error, got %v", marshal_err) + + my_map2 := make(map[i32]string) + defer delete_map(my_map2) + + unmarshal_err := json.unmarshal(marshaled_data, &my_map2) + defer for key, item in my_map2 { + runtime.delete_string(item) + } + testing.expectf(t, unmarshal_err == nil, "Expected `json.unmarshal` to return nil, got %v", unmarshal_err) + + testing.expectf(t, len(my_map) == len(my_map2), "Expected %v map items to have been unmarshaled, got %v", len(my_map), len(my_map2)) + + for key, item in my_map { + testing.expectf(t, key in my_map2, "Expected key %v to be present in unmarshaled map", key) + + value_from_map2, ok := my_map2[key] + if ok { + testing.expectf(t, runtime.string_eq(item, my_map2[key]), "Expected value %s to be present in unmarshaled map", key) + } + } } \ No newline at end of file From 39983eaaa44a2177ca9b05cd76c29d96924a7e81 Mon Sep 17 00:00:00 2001 From: VladPavliuk Date: Sat, 13 Jul 2024 00:26:54 +0300 Subject: [PATCH 2/3] Remove unused imports in `test_core_json` --- tests/core/encoding/json/test_core_json.odin | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/core/encoding/json/test_core_json.odin b/tests/core/encoding/json/test_core_json.odin index dfc655af0..bf81330b6 100644 --- a/tests/core/encoding/json/test_core_json.odin +++ b/tests/core/encoding/json/test_core_json.odin @@ -3,10 +3,7 @@ package test_core_json import "core:encoding/json" import "core:testing" import "core:mem/virtual" -import "core:fmt" import "base:runtime" -import "core:log" -import "core:strings" @test parse_json :: proc(t: ^testing.T) { From 79e2f63182581547dcdb7593397d1c3e280a5670 Mon Sep 17 00:00:00 2001 From: VladPavliuk Date: Sat, 13 Jul 2024 00:38:58 +0300 Subject: [PATCH 3/3] Small code refactoring in `test_core_json` --- tests/core/encoding/json/test_core_json.odin | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/core/encoding/json/test_core_json.odin b/tests/core/encoding/json/test_core_json.odin index bf81330b6..62f474ce0 100644 --- a/tests/core/encoding/json/test_core_json.odin +++ b/tests/core/encoding/json/test_core_json.odin @@ -400,8 +400,7 @@ map_with_integer_keys :: proc(t: ^testing.T) { for key, item in my_map { testing.expectf(t, key in my_map2, "Expected key %v to be present in unmarshaled map", key) - value_from_map2, ok := my_map2[key] - if ok { + if key in my_map2 { testing.expectf(t, runtime.string_eq(item, my_map2[key]), "Expected value %s to be present in unmarshaled map", key) } }