-
Notifications
You must be signed in to change notification settings - Fork 0
/
json.go
137 lines (125 loc) · 3.07 KB
/
json.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package json_utils
import (
"strings"
)
// Delete comments and BOM (if any) from JSON
func FixJson(b []byte) []byte {
b = RemoveBom(b)
b = stripComments(b)
return b
}
// States
const (
emptyState = iota
stringState
singleLineCommentState
multilineCommentState
)
// Remove comments with LL(2) lexer
func stripComments(b []byte) []byte {
state := emptyState
offset := 0
var buffer strings.Builder
var result strings.Builder
commaIndex := -1
for index, n := 0, len(b); index < n; index++ {
ch := getAt(b, index)
nextCh := getAt(b, index + 1)
if checkState(state, emptyState) {
// select next state
if ch == '"' {
// string
state = stringState
if commaIndex != -1 {
commaIndex = -1
}
} else if ch == '/' {
// may be a comment
if nextCh == '/' {
// single line comment
buffer.Write(b[offset:index])
offset = index
state = singleLineCommentState
index++;
} else if nextCh == '*' {
// multiline comment
buffer.Write(b[offset:index])
offset = index
state = multilineCommentState
index++
}
} else if commaIndex != -1 {
if ch == '}' || ch == ']' {
// deleting unnecessary commas
buffer.Write(b[offset:index])
result.Write([]byte(buffer.String())[1:])
buffer.Reset()
offset = index
commaIndex = -1
} else if ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n' {
// conditionally non-whitespace character after the comma
buffer.Write(b[offset:index])
offset = index
commaIndex = -1
}
} else if ch == ',' {
buffer.Write(b[offset:index])
result.Write([]byte(buffer.String()))
buffer.Reset()
offset = index
commaIndex = index
}
} else if checkState(state, stringState) {
// string could be closed only by non shielded "
if ch == '"' && !isEscaped(b, index) {
state = emptyState
}
} else if checkState(state, singleLineCommentState) {
// is single line comment could be finished
if ch == '\r' {
// finishing by \r(\n)?
offset = index
state = emptyState
if nextCh == '\n' {
index++
}
} else if ch == '\n' {
// finishing by \n
state = emptyState
offset = index
}
} else if checkState(state, multilineCommentState) {
// multiline comment finished only by paired */
if ch == '*' && nextCh == '/' {
index++
state = emptyState
offset = index + 1
}
}
}
if checkState(state, emptyState) {
buffer.Write(b[offset:])
}
result.Write([]byte(buffer.String()))
return []byte(result.String())
}
// Get byte value in given position.
func getAt(b []byte, pos int) (r byte) {
if pos < 0 || pos >= len(b) {
return
}
r = b[pos]
return
}
// Check whether symbol is shielded.
func isEscaped(b []byte, pos int) bool {
backslashCount := 0
for pos--; pos >= 0 && b[pos] == '\\'; pos-- {
backslashCount++
}
return backslashCount % 2 != 0
}
// Check state.
func checkState(state, mode int) bool {
return state == mode
}