geko provides GEneric Keep Order types.
It's mainly used to solve the issue that in some scenarios, the field order in JSON object is meaningful, but when unmarshal into a normal map, these information will be lost. See golang/go#27179.
There are many projects trying to solve it, but most of them lack some features that I need, see bellow.
- Not limited to JSON processing, can also be used as container with insertion order preservation feature.
- Generics, use as little reflection as possible, for better performance,
- Customizable strategy to deal with duplicated key, auto deduplication.
- Option to use
json.Number
to preserve the full precision of number field. - Very tiny, 0 dependencies.
- Fully tested, keep 100% coverage.
Status: Beta. All features I need are implemented and tested, But API design may not be the final version.
result, _ := geko.JSONUnmarshal([]byte(`{"a": 1, "b": 2, "a": 3}`))
object := result.(geko.ObjectItems)
output, _ := json.Marshal(object)
fmt.Println(string(output)) // {"a":1,"b":2,"a":3}
The ObjectItems
is type alias of *Pairs[string, any]
, which is a wrapper for []Pair[string, any]
. Because it's a slice under the hood, so it can store all items in JSON object.
If you don't want duplicated key in result, try UseObject
:
result, _ := geko.JSONUnmarshal(
[]byte(`{"a": 1, "b": 2, "a": 3}`),
geko.UseObject(),
)
object := result.(geko.Object)
object.Keys() // => ["a", "b"]
output, _ := json.Marshal(object)
fmt.Println(string(output)) // {"a":3,"b":2}
UseObject
will make JSONUnmarshal
use Object
to deal with JSON Object, it is alias of *Map[string, any]
.
You may find it weird that a
has a value of 3
, this behavior can be controlled by add a option geko.ObjectOnDuplicatedKey(strategy)
:
for input {"a": 1, "b": 2, "a": 3}
strategy | result(space added) | note |
---|---|---|
UpdateValueKeepOrder |
{"a": 3, "b": 2} |
default strategy |
UpdateValueUpdateOrder |
{"b": 2, "a": 3} |
keep the last occurrence |
KeepValueUpdateOrder |
{"b": 2, "a": 1} |
|
Ignore |
{"a": 1, "b": 2} |
keep the first occurrence |
The UpdateValueKeepOrder
is chosen as default strategy because it matches the behavior of NodeJS.
> const obj = JSON.parse('{"a": 1, "b": 2, "a": 3}')
> obj.a
3
> JSON.stringify(obj)
'{"a":3,"b":2}'
>
The type parameters do not limited to [string, any]
, you can use other type you want.
m := geko.NewMap[string, int]()
m.Set("one", 1)
m.Set("three", 2)
m.Set("two", 2)
m.Set("three", 3) // Set always do not change order of existed key, so "three" will stay ahead of "two".
m.Set("four", 0)
m.Set("five", 5)
m.SetDuplicatedKeyStrategy(geko.UpdateValueUpdateOrder)
m.Add("four", 4) // Add will follow DuplicatedKeyStrategy, so now four is last key, and it's value is 4
for i, length := 0, m.Len(); i < length; i++ {
pair := m.GetByIndex(i)
fmt.Printf("%s: %d\n", pair.Key, pair.Value)
}
Outputs:
one: 1
three: 3
two: 2
five: 5
four: 4
m := geko.NewPairs[string, int]()
m.Add("one", 1)
m.Add("three", 2)
m.Add("two", 2)
m.Add("three", 3)
for i, length := 0, m.Len(); i < length; i++ {
pair := m.GetByIndex(i)
fmt.Printf("%s: %d\n", pair.Key, pair.Value)
}
fmt.Println("-----")
m.Dedup(geko.Ignore)
for i, length := 0, m.Len(); i < length; i++ {
pair := m.GetByIndex(i)
fmt.Printf("%s: %d\n", pair.Key, pair.Value)
}
Outputs:
one: 1
three: 2
two: 2
three: 3
-----
one: 1
three: 2
two: 2
See Document for detail of all APIs.
MIT. See LICENSE file.