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 marshal implementation #50

Closed
wants to merge 1 commit into from
Closed
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
58 changes: 58 additions & 0 deletions v2/marshal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package orderedmap

import (
"bytes"
"encoding"
"encoding/json"
"fmt"
"reflect"
"strconv"
)

func (m *OrderedMap[K, V]) MarshalJSON() ([]byte, error) {
b := bytes.NewBuffer(make([]byte, 0, m.Len()<<3))
b.WriteByte('{')
for el := m.Front(); el != nil; el = el.Next() {
keyValue := reflect.ValueOf(el.Key)
keyStr, err := resolveKeyName(keyValue)
if err != nil {
return nil, fmt.Errorf("error resolving key: %w", err)
}
key, err := json.Marshal(keyStr)
if err != nil {
return nil, fmt.Errorf("error marshaling key %v: %w", el.Key, err)
}
b.Write(key)
b.WriteByte(':')
value, err := json.Marshal(el.Value)
if err != nil {
return nil, fmt.Errorf("error marshaling value for key %v: %w", el.Key, err)
}
b.Write(value)
if el.Next() != nil {
b.WriteByte(',')
}
}
b.WriteByte('}')
return b.Bytes(), nil
}

func resolveKeyName(k reflect.Value) (string, error) {
if k.Kind() == reflect.String {
return k.String(), nil
}
if tm, ok := k.Interface().(encoding.TextMarshaler); ok {
if k.Kind() == reflect.Pointer && k.IsNil() {
return "", nil
}
buf, err := tm.MarshalText()
return string(buf), err
}
switch k.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(k.Int(), 10), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return strconv.FormatUint(k.Uint(), 10), nil
}
return "", &json.UnsupportedTypeError{Type: k.Type()}
}
144 changes: 144 additions & 0 deletions v2/orderedmap_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package orderedmap_test

import (
"encoding/json"
"strconv"
"testing"

Expand Down Expand Up @@ -999,3 +1000,146 @@ func BenchmarkAll(b *testing.B) {
BenchmarkBigOrderedMapString_Iterate)
b.Run("BenchmarkBigMapString_Iterate", BenchmarkBigMapString_Iterate)
}

func TestJsonMarshal(t *testing.T) {
tests := []struct {
name string
encodeFn func() ([]byte, error)
expected string
errorExpected bool
}{
{
name: "key in string type",
encodeFn: func() ([]byte, error) {
m := orderedmap.NewOrderedMap[string, int]()
m.Set("one", 1)
m.Set("two", 2)
m.Set("three", 3)
return json.Marshal(m)
},
expected: `{"one":1,"two":2,"three":3}`,
errorExpected: false,
},
{
name: "key in int type",
encodeFn: func() ([]byte, error) {
m := orderedmap.NewOrderedMap[int, string]()
m.Set(3, "three")
m.Set(2, "two")
m.Set(1, "one")
return json.Marshal(m)
},
expected: `{"3":"three","2":"two","1":"one"}`,
errorExpected: false,
},
{
name: "key in uint type",
encodeFn: func() ([]byte, error) {
m := orderedmap.NewOrderedMap[uint, string]()
m.Set(3, "three")
m.Set(2, "two")
m.Set(1, "one")
return json.Marshal(m)
},
expected: `{"3":"three","2":"two","1":"one"}`,
errorExpected: false,
},
{
name: "nil values",
encodeFn: func() ([]byte, error) {
m := orderedmap.NewOrderedMap[string, *string]()
m.Set("one", nil)
m.Set("two", nil)
m.Set("three", nil)
return json.Marshal(m)
},
expected: `{"one":null,"two":null,"three":null}`,
errorExpected: false,
},
{
name: "nil map",
encodeFn: func() ([]byte, error) {
var m *orderedmap.OrderedMap[string, string]
return json.Marshal(m)
},
expected: `null`,
errorExpected: false,
},
{
name: "empty map",
encodeFn: func() ([]byte, error) {
return json.Marshal(orderedmap.NewOrderedMap[string, string]())
},
expected: `{}`,
errorExpected: false,
},
{
name: "key containing quote",
encodeFn: func() ([]byte, error) {
m := orderedmap.NewOrderedMap[string, string]()
m.Set(`key"with"quotes`, "value")
return json.Marshal(m)
},
expected: `{"key\"with\"quotes":"value"}`,
errorExpected: false,
},
{
name: "key with special characters",
encodeFn: func() ([]byte, error) {
m := orderedmap.NewOrderedMap[string, string]()
m.Set("key\nwith\nnewlines", "value")
return json.Marshal(m)
},
expected: `{"key\nwith\nnewlines":"value"}`,
errorExpected: false,
},
{
name: "key in unsupported type",
encodeFn: func() ([]byte, error) {
type Key struct {
Id int64 `json:"id"`
}
m := orderedmap.NewOrderedMap[Key, string]()
m.Set(Key{Id: 0}, "value")
return json.Marshal(m)
},
expected: "",
errorExpected: true,
},
{
name: "key in custom type",
encodeFn: func() ([]byte, error) {
type Key int64
m := orderedmap.NewOrderedMap[Key, string]()
m.Set(Key(0), "value")
return json.Marshal(m)
},
expected: `{"0":"value"}`,
errorExpected: false,
},
{
name: "nested map",
encodeFn: func() ([]byte, error) {
inner := orderedmap.NewOrderedMap[string, int]()
inner.Set("innerKey", 42)
outer := orderedmap.NewOrderedMap[string, *orderedmap.OrderedMap[string, int]]()
outer.Set("outerKey", inner)
return json.Marshal(outer)
},
expected: `{"outerKey":{"innerKey":42}}`,
errorExpected: false,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
b, err := test.encodeFn()
if test.errorExpected {
assert.Error(t, err)
return
}
assert.Nil(t, err)
assert.Equal(t, test.expected, string(b))
})
}
}