1# go-jsons
2
3A universal `JSON` merge library for `Go`.
4
5(test coverage: 100.0%)
6
7## Installation
8
9```bash
10go get github.com/qjebbs/go-jsons
11```
12
13## Usage
14
15```go
16a := []byte(`{"a":1}`)
17b := []byte(`{"b":[1]}`)
18c := []byte(`{"b":[2]}`)
19got, err := jsons.Merge(a, b, c) // got = []byte(`{"a":1,"b":[1,2]}`)
20```
21
22### Accepted input
23
24- `string`: path to a local file
25- `[]string`: paths of local files
26- `[]byte`: content of a file
27- `[][]byte`: content list of files
28- `io.Reader`: content reader
29- `[]io.Reader`: content readers
30
31## Merge rules
32
33The strandard merger has only one rule which is intuitive and easy to understand:
34
35- Simple values (`string`, `number`, `boolean`) are overwritten, others (`array`, `object`) are merged.
36
37To work with complex files and contents, you can create a custom merger to applies more rules:
38
39```go
40var myMerger = NewMerger(
41 rule.MergeBy("tag"),
42 rule.MergeByAndRemove("_tag"),
43 rule.OrderByAndRemove("_order"),
44)
45myMerger.Merge("a.json", "b.json")
46```
47
48which means:
49
50- Elements with same `tag` or `_tag` in an array will be merged.
51- Elements in an array will be sorted by the value of `_order` field, the smaller ones are in front.
52
53> `_tag` and `_order` fields will be removed after merge, according to the codes above.
54
55Suppose we have...
56
57`a.json`:
58
59```json
60{
61 "log": {"level": "debug"},
62 "inbounds": [{"tag": "in-1"}],
63 "outbounds": [{"_order": 100, "tag": "out-1"}],
64 "route": {"rules": [
65 {"_tag":"rule1","inbound":["in-1"],"outbound":"out-1"}
66 ]}
67}
68```
69
70`b.json`:
71
72```json
73{
74 "log": {"level": "error"},
75 "outbounds": [{"_order": -100, "tag": "out-2"}],
76 "route": {"rules": [
77 {"_tag":"rule1","inbound":["in-1.1"],"outbound":"out-1.1"}
78 ]}
79}
80```
81
82Output:
83
84```jsonc
85{
86 // level field is overwritten by the latter value
87 "log": {"level": "error"},
88 "inbounds": [{"tag": "in-1"}],
89 "outbounds": [
90 // Although out-2 is a latecomer, but it's in
91 // the front due to the smaller "_order"
92 {"tag": "out-2"},
93 {"tag": "out-1"}
94 ],
95 "route": {"rules": [
96 // 2 rules are merged into one due to the same "_tag",
97 // outbound field is overwritten during the merging
98 {"inbound":["in-1","in-1.1"],"outbound":"out-1.1"}
99 ]}
100}
101```
102
103## Load from other formats
104
105`go-jsons` allows you to extend it to load other formats easily.
106
107For example, to load from `YAML` files and merge to `JSON`:
108
109```go
110package main
111
112import (
113 "fmt"
114
115 "github.com/qjebbs/go-jsons"
116 // yaml v3 is required, since v2 generates `map[interface{}]interface{}`,
117 // which is not compatible with json.Marshal
118 "gopkg.in/yaml.v3"
119)
120
121func main() {
122 const FormatYAML jsons.Format = "yaml"
123 m := jsons.NewMerger()
124 m.RegisterLoader(
125 FormatYAML,
126 []string{".yaml", ".yml"},
127 func(b []byte) (map[string]interface{}, error) {
128 m := make(map[string]interface{})
129 err := yaml.Unmarshal(b, &m)
130 if err != nil {
131 return nil, err
132 }
133 return m, nil
134 },
135 )
136 a := []byte(`{"a": 1}`) // json
137 b := []byte(`b: 1`) // yaml
138 got, err := m.Merge(a, b)
139 if err != nil {
140 panic(err)
141 }
142 fmt.Println(string(got)) // {"a":1,"b":1}
143}
144```
145
146## Why not support remote files?
147
148Here are my considerations:
149
150- It makes the your program support remote file unexpectedly, which may be a security risk.
151- Users need to choose their own strategy for loading remote files, not hard-coded logic in the library
152- You can still merge downloaded content by `[]byte` or `io.Reader`