1+ // package jsonc provides APIs to read commented (.jsonc) files.
2+ // It implements io.Reader which makes it compatible with standard library.
3+
4+ // EX.
5+ // ...
6+ // ba, _ := ioutil.Readall(jsonc.NewDecoder(r)) // r is io.Reader
7+ // ...
8+
19package jsonc
210
311import (
@@ -8,19 +16,27 @@ import (
816)
917
1018type (
11- state int
19+ // Decoder represents a .jsonc decoder. It implements io.Reader.
20+ Decoder struct {
21+ r io.Reader
22+ c comment
23+ }
1224
25+ // comment represents status of Decoder while reading json document.
1326 comment struct {
1427 state state
15- multiLn bool
16- isJSON bool
28+ multiLn bool // if true, a multiline comment
29+ isJSON bool // if true, decoder is processing either a "key" or "value"
1730 }
1831
19- // Decoder implements io.Reader. It wraps provided source.
20- Decoder struct {
21- r io.Reader
22- c comment
23- }
32+ state int
33+ )
34+
35+ const (
36+ stopped state = iota // 0, default state, started with 0 to make `zero value` of `comment` useful.
37+ canStart // 1
38+ started // 2
39+ canStop // 3
2440)
2541
2642const (
@@ -35,118 +51,107 @@ const (
3551 charN = 110 // (n)
3652)
3753
38- const (
39- stopped state = iota // 0, default state
40- canStart // 1
41- started // 2
42- canStop // 3
43- )
44-
4554var (
4655 ErrUnexpectedEndOfComment = errors .New ("unexpected end of comment" )
4756)
4857
49- // NewDecoder returns a new Decoder wrapping the provided io.Reader. The returned decoder implements io.Reader.
50- func NewDecoder (r io.Reader ) * Decoder {
51- return & Decoder {
52- c : comment {},
53- r : r ,
54- }
58+ func (c * comment ) reset () {
59+ c .state = stopped
60+ c .multiLn = false
5561}
5662
57- // Read reads from underlying reader and processes the stream to omit comments.
58- // A single read doesn't guaranttee a valid JSON. Depends on length of passed slice.
59- //
60- // Produces ErrUnexpectedEndOfComment for incomplete comments.
61- func (d * Decoder ) Read (p []byte ) (int , error ) {
62-
63- n , err := d .r .Read (p )
64- if err != nil {
65- return 0 , err
66- }
67-
68- shortRead := n <= len (p )
69- n = decode (p [:n ], & d .c )
70-
71- if shortRead && ! d .c .complete () {
72- return 0 , ErrUnexpectedEndOfComment
73- }
74-
75- return n , nil
63+ func (c * comment ) complete () bool {
64+ return c .state == stopped
7665}
7766
7867func decode (p []byte , c * comment ) int {
7968 i := 0
8069 for _ , s := range p {
81- if c .handle (s ) {
82- p [i ] = s
83- i ++
84- }
85- }
8670
87- return i
88- }
71+ switch c .state {
8972
90- // handle the current byte, if returned true, add the byte to result.
91- func (c * comment ) handle (s byte ) bool {
92- switch c .state {
73+ case stopped :
74+ if s == quote { // all characters between "" are valid, can be added to result
75+ c .isJSON = ! c .isJSON
76+ }
9377
94- case stopped :
95- if s == quote { // all characters between "" are valid, can be added to result
96- c .isJSON = ! c .isJSON
97- }
78+ if c .isJSON {
79+ p [i ] = s
80+ i ++
81+ continue
82+ }
9883
99- if c .isJSON {
100- return true
101- }
84+ // TODO: formatting can be preserved by allowing space and tab in JSON
85+ if s == space || s == tab || s == newLine {
86+ continue
87+ }
10288
103- if s == space || s == tab || s == newLine {
104- return false
105- }
89+ if s == fwdSlash {
90+ c .state = canStart
91+ continue
92+ }
10693
107- if s == fwdSlash {
108- c .state = canStart
109- return false
110- }
94+ p [i ] = s
95+ i ++
11196
112- return true
97+ case canStart :
11398
114- case canStart :
99+ if s == star || s == fwdSlash {
100+ c .state = started
101+ }
115102
116- if s == star || s == fwdSlash {
117- c .state = started
118- }
103+ c .multiLn = (s == star )
119104
120- c . multiLn = ( s == star )
105+ case started :
121106
122- case started :
107+ if s == star || s == bkdSlash {
108+ c .state = canStop
109+ }
123110
124- if s == star || s == bkdSlash {
125- c . state = canStop
126- }
111+ if s == newLine && ! c . multiLn {
112+ c . reset ()
113+ }
127114
128- if s == newLine && ! c .multiLn {
129- c .reset ()
130- }
115+ case canStop :
131116
132- case canStop :
117+ if s == fwdSlash || s == charN {
118+ c .reset ()
119+ }
133120
134- if s == fwdSlash || s == charN {
135- c .reset ()
136121 }
137122
138123 }
139124
140- return false
125+ return i
141126}
142127
143- func (c * comment ) reset () {
144- c .state = stopped
145- c .multiLn = false
128+ // NewDecoder returns a new Decoder wrapping the provided io.Reader.
129+ func NewDecoder (r io.Reader ) * Decoder {
130+ return & Decoder {
131+ c : comment {},
132+ r : r ,
133+ }
146134}
147135
148- func (c * comment ) complete () bool {
149- return c .state == stopped
136+ // Read reads from underlying reader and processes the stream to omit comments.
137+ // A single read doesn't guaranttee a valid JSON. Depends on length of passed slice.
138+ //
139+ // Produces ErrUnexpectedEndOfComment for incomplete comments else if not nil, then it is an error from underlying reader.
140+ func (d * Decoder ) Read (p []byte ) (int , error ) {
141+
142+ n , err := d .r .Read (p )
143+ if err != nil {
144+ return 0 , err
145+ }
146+
147+ shortRead := n <= len (p ) // data to be processed is less than length of `p`
148+ n = decode (p [:n ], & d .c )
149+
150+ if shortRead && ! d .c .complete () {
151+ return 0 , ErrUnexpectedEndOfComment
152+ }
153+
154+ return n , nil
150155}
151156
152157// DecodeBytes decodes passed commented json byte slice to normal json.
0 commit comments