https://github.com/7sdream/geko
GEneric Keep Order types for JSON (un)marshal.
https://github.com/7sdream/geko
generic golang json
Last synced: about 1 year ago
JSON representation
GEneric Keep Order types for JSON (un)marshal.
- Host: GitHub
- URL: https://github.com/7sdream/geko
- Owner: 7sDream
- License: mit
- Created: 2023-08-12T22:47:15.000Z (almost 3 years ago)
- Default Branch: master
- Last Pushed: 2023-08-23T03:26:24.000Z (almost 3 years ago)
- Last Synced: 2025-03-28T02:53:12.853Z (over 1 year ago)
- Topics: generic, golang, json
- Language: Go
- Homepage: https://pkg.go.dev/github.com/7sDream/geko
- Size: 218 KB
- Stars: 7
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: Changelog.md
- License: LICENSE
Awesome Lists containing this project
README
# geko
![version-badge] [![build-badge]][workflow] ![coverage-badge] ![license-badge] [![document-badge]][document]
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.
## Features
- 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.
## Usage
### JSON processing
```golang
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`:
```golang
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]`.
### Duplicated key strategy
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.
```text
> const obj = JSON.parse('{"a": 1, "b": 2, "a": 3}')
> obj.a
3
> JSON.stringify(obj)
'{"a":3,"b":2}'
>
```
### Generic container
The type parameters do not limited to `[string, any]`, you can use other type you want.
#### Map
```golang
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:
```text
one: 1
three: 3
two: 2
five: 5
four: 4
```
#### Pairs
``` golang
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:
```text
one: 1
three: 2
two: 2
three: 3
-----
one: 1
three: 2
two: 2
```
See [Document] for detail of all APIs.
## LICENSE
MIT. See LICENSE file.
[version-badge]: https://img.shields.io/github/v/tag/7sDream/geko?filter=v*&style=for-the-badge&label=version
[build-badge]: https://img.shields.io/github/actions/workflow/status/7sDream/geko/go.yml?style=for-the-badge
[workflow]: https://github.com/7sDream/geko/actions/workflows/go.yml
[coverage-badge]: https://raw.githubusercontent.com/7sDream/geko/badges/.badges/master/coverage.svg
[license-badge]: https://img.shields.io/github/license/7sDream/geko?style=for-the-badge
[document-badge]: https://img.shields.io/badge/-Document-blue?style=for-the-badge&logo=readthedocs
[document]: https://pkg.go.dev/github.com/7sDream/geko
[golang/go#27179]: https://github.com/golang/go/issues/27179