A universal JSON merge library for Go.
(test coverage: 100.0%)
go get github.com/qjebbs/go-jsonsa := []byte(`{"a":1}`)
b := []byte(`{"b":[1]}`)
c := []byte(`{"b":[2]}`)
got, err := jsons.Merge(a, b, c) // got = []byte(`{"a":1,"b":[1,2]}`)string: path to a local file[]string: paths of local files[]byte: content of a file[][]byte: content list of filesio.Reader: content reader[]io.Reader: content readers
The strandard merger is intuitive and easy to understand:
- Simple values (
string,number,boolean) are overwritten by later ones. - Container values (
object,array) are merged recursively.
To work with complex contents, you may create a custom merger to applies more options:
var myMerger = jsons.NewMerger(
jsons.WithMergeBy("tag"),
jsons.WithMergeByAndRemove("_tag"),
jsons.WithOrderByAndRemove("_order"),
)
myMerger.Merge("a.json", "b.json")which means:
- Elements with same
tagor_tagin an array will be merged. - Elements in an array will be sorted by the value of
_orderfield, the smaller ones are in front.
_tagand_orderfields will be removed after merge, according to the codes above.
Suppose we have...
a.json:
{
"log": {"level": "debug"},
"inbounds": [{"tag": "in-1"}],
"outbounds": [{"_order": 100, "tag": "out-1"}],
"route": {"rules": [
{"_tag":"rule1","inbound":["in-1"],"outbound":"out-1"}
]}
}b.json:
{
"log": {"level": "error"},
"outbounds": [{"_order": -100, "tag": "out-2"}],
"route": {"rules": [
{"_tag":"rule1","inbound":["in-1.1"],"outbound":"out-1.1"}
]}
}Output:
go-jsons allows you to extend it to load other formats easily.
For example, to load from YAML files and merge to JSON:
package main
import (
"fmt"
"github.com/qjebbs/go-jsons"
// goccy/go-yaml is able to use json.Unmarshaler
"github.com/goccy/go-yaml"
)
func ExampleMerger_RegisterLoader() {
const FormatYAML jsons.Format = "yaml"
m := jsons.NewMerger()
m.RegisterOrderedLoader(
FormatYAML,
[]string{".yaml", ".yml"},
func(b []byte) (*jsons.OrderedMap, error) {
// YAML fields order will be kept
m := jsons.NewOrderedMap()
err := yaml.UnmarshalWithOptions(
b, m,
// important
yaml.UseJSONUnmarshaler(),
)
if err != nil {
return nil, err
}
return m, nil
},
)
a := []byte(`{"a":1,"z":1}`) // json
b := []byte("b: 1\nc: 1\nd: 1") // yaml
got, err := m.Merge(a, b)
if err != nil {
panic(err)
}
fmt.Println(string(got))
// Output: {"a":1,"z":1,"b":1,"c":1,"d":1}
}Here are some considerations:
- It makes the your program support remote file unexpectedly, which may be a security risk.
- Users need to choose their own strategy for loading remote files, not hard-coded logic in the library
- You can still merge downloaded content by
[]byteorio.Reader
{ // level field is overwritten by the latter value "log": {"level": "error"}, "inbounds": [{"tag": "in-1"}], "outbounds": [ // Although out-2 is a latecomer, but it's in // the front due to the smaller "_order" {"tag": "out-2"}, {"tag": "out-1"} ], "route": {"rules": [ // 2 rules are merged into one due to the same "_tag", // outbound field is overwritten during the merging {"inbound":["in-1","in-1.1"],"outbound":"out-1.1"} ]} }