Skip to content

Commit 810fe03

Browse files
authored
Merge pull request #2 from adhocore/1-json5
Implement most json5 spec
2 parents 137689a + 5d4f05f commit 810fe03

9 files changed

+3573
-95
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
.idea/
22
.DS_Store
33
*~
4+
*.cached.json
5+
*.exe
46
*.out
7+
*.prof
58
vendor/
69
dist/
710
.env

README.md

+100-6
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,74 @@
88
[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Lightweight+fast+and+deps+free+commented+json+parser+for+Golang&url=https://github.com/adhocore/jsonc&hashtags=go,golang,parser,json,json-comment)
99

1010

11-
- Lightweight JSON comment stripper library for Go.
11+
- Lightweight [JSON5](https://json5.org) pre-processor library for Go.
12+
- Parses JSON5 input to JSON that Go understands. (Think of it as a superset to JSON.)
1213
- Makes possible to have comment in any form of JSON data.
1314
- Supported comments: single line `// comment` or multi line `/* comment */`.
14-
- Also strips trailing comma at the end of array or object, eg:
15+
- Supports trailing comma at the end of array or object, eg:
1516
- `[1,2,,]` => `[1,2]`
1617
- `{"x":1,,}` => `{"x":1}`
17-
- Handles literal LF (newline/linefeed) within string notation so that we can have multiline string
18-
- Supports JSON string inside JSON string
18+
- Supports single quoted string.
19+
- Supports object keys without quotes.
20+
- Handles literal LF (linefeed) in string for splitting long lines.
21+
- Supports explicit positive and hex number. `{"change": +10, "hex": 0xffff}`
22+
- Supports decimal numbers with leading or trailing period. `{"leading": .5, "trailing": 2.}`
23+
- Supports JSON string inside JSON string.
1924
- Zero dependency (no vendor bloat).
2025

26+
---
27+
### Example
28+
29+
This is [example](./examples/test.json5) of the JSON that you can parse with `adhocore/jsonc`:
30+
31+
```json5
32+
/*start*/
33+
//..
34+
{
35+
// this is line comment
36+
a: [ // unquoted key
37+
'bb', // single quoted string
38+
"cc", // double quoted string
39+
/* multi line
40+
* comment
41+
*/
42+
123, // number
43+
+10, // +ve number, equivalent to 10
44+
-20, // -ve number
45+
.25, // floating number, equivalent to 0.25
46+
5., // floating number, equivalent to 5.0
47+
0xabcDEF, // hex base16 number, equivalent to base10 counterpart: 11259375
48+
{
49+
123: 0xf, // even number as key?
50+
xx: [1, .1, 'xyz',], y: '2', // array inside object, inside array
51+
},
52+
"// not a comment",
53+
"/* also not a comment */",
54+
['', "", true, false, null, 1, .5, 2., 0xf, // all sort of data types
55+
{key:'val'/*comment*/,}], // object inside array, inside array
56+
'single quoted',
57+
],
58+
/*aa*/aa: ['AA', {in: ['a', "b", ],},],
59+
'd': { // single quoted key
60+
t: /*creepy comment*/true, 'f': false,
61+
a_b: 1, _1_z: 2, Ḁẟḕỻ: 'ɷɻɐỨẞṏḉ', // weird keys?
62+
"n": null /*multiple trailing commas?*/,,,
63+
/* 1 */
64+
/* 2 */
65+
},
66+
"e": 'it\'s "good", eh?', // double quoted key, single quoted value with escaped quote
67+
// json with comment inside json with comment, read that again:
68+
"g": "/*comment*/{\"i\" : 1, \"url\" : \"http://foo.bar\" }//comment",
69+
"h": "a new line after word 'literal'
70+
this text is in a new line as there is literal EOL just above. \
71+
but this one is continued in same line due to backslash escape",
72+
// 1.
73+
// 2.
74+
}
75+
//..
76+
/*end*/
77+
```
78+
2179
Find jsonc in [pkg.go.dev](https://pkg.go.dev/github.com/adhocore/jsonc).
2280

2381
## Installation
@@ -26,13 +84,19 @@ Find jsonc in [pkg.go.dev](https://pkg.go.dev/github.com/adhocore/jsonc).
2684
go get -u github.com/adhocore/jsonc
2785
```
2886

87+
## Usecase
88+
89+
You would ideally use this for organizing JSON configurations for humans to read and manage.
90+
The JSON5 input is processed down into JSON which can be Unmarshal'ed by `encoding/json`.
91+
92+
For performance reasons you may also use [cached decoder](#cached-decoder) to have a cached copy of processed JSON output.
93+
2994
## Usage
3095

3196
Import and init library:
3297
```go
3398
import (
3499
"fmt"
35-
36100
"github.com/adhocore/jsonc"
37101
)
38102

@@ -84,12 +148,42 @@ j.UnmarshalFile("./examples/test.json5", &out)
84148
fmt.Printf("%+v\n", out)
85149
```
86150

151+
### Cached Decoder
152+
153+
If you are weary of parsing same JSON5 source file over and over again, you can use cached decoder.
154+
The source file is preprocessed and cached into output file with extension `.cached.json`.
155+
It syncs the file `mtime` (aka modified time) from JSON5 source file to the cached JSON file to detect change.
156+
157+
The output file can then be consumed readily by `encoding/json`.
158+
Leave that cached output untouched for machine and deal with source file only.
159+
> (You can add `*.cached.json` to `.gitignore` if you so wish.)
160+
161+
As an example [examples/test.json5](./examples/test.json5) will be processed and cached into `examples/test.cached.json`.
162+
163+
Every change in source file `examples/test.json5` is reflected to the cached output on next call to `Decode()`
164+
thus always maintaining the sync.
165+
166+
```go
167+
import (
168+
"fmt"
169+
"github.com/adhocore/jsonc"
170+
)
171+
172+
var dest map[string]interface{}
173+
err := jsonc.NewCachedDecoder().Decode("./examples/test.json5", &dest);
174+
if err != nil {
175+
fmt.Printf("%+v", err)
176+
} else {
177+
fmt.Printf("%+v", dest)
178+
}
179+
```
180+
87181
> Run working [examples](./examples/main.go) with `go run examples/main.go`.
88182
89183
---
90184
## License
91185

92-
> © [MIT](./LICENSE) | 2021-2099, Jitendra Adhikari
186+
> © [MIT](./LICENSE) | 2022-2099, Jitendra Adhikari
93187
94188
---
95189
### Other projects

decode.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package jsonc
2+
3+
import (
4+
"encoding/json"
5+
"io/ioutil"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
)
10+
11+
// CachedDecoder is a managed decoder that caches a copy of json5 transitioned to json
12+
type CachedDecoder struct {
13+
jsonc *Jsonc
14+
ext string
15+
}
16+
17+
// NewCachedDecoder gives a cached decoder
18+
func NewCachedDecoder(ext ...string) *CachedDecoder {
19+
ext = append(ext, ".cached.json")
20+
return &CachedDecoder{New(), ext[0]}
21+
}
22+
23+
// Decode decodes from cache if exists and relevant else decodes from source
24+
func (fd *CachedDecoder) Decode(file string, v interface{}) error {
25+
stat, err := os.Stat(file)
26+
if err != nil {
27+
return err
28+
}
29+
30+
cache := strings.TrimSuffix(file, filepath.Ext(file)) + fd.ext
31+
cstat, err := os.Stat(cache)
32+
exist := !os.IsNotExist(err)
33+
if err != nil && exist {
34+
return err
35+
}
36+
37+
// Update if not exist, or source file modified
38+
update := !exist || stat.ModTime() != cstat.ModTime()
39+
if !update {
40+
jsonb, _ := ioutil.ReadFile(cache)
41+
return json.Unmarshal(jsonb, v)
42+
}
43+
44+
jsonb, _ := ioutil.ReadFile(file)
45+
cfile, err := os.Create(cache)
46+
if err != nil {
47+
return err
48+
}
49+
50+
jsonb = fd.jsonc.Strip(jsonb)
51+
cfile.Write(jsonb)
52+
os.Chtimes(cache, stat.ModTime(), stat.ModTime())
53+
return json.Unmarshal(jsonb, v)
54+
}

0 commit comments

Comments
 (0)