Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 14 additions & 32 deletions jsonc.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import (
"encoding/json"
)

// Unmarshal parses the JSON-encoded data with comment support and stores the result in the value pointed to by v.
func Unmarshal(data []byte, v interface{}) error {
processedData := decomment(data)
processedData := stripComments(data)
return json.Unmarshal(processedData, v)
}

func decomment(data []byte) []byte {
func stripComments(data []byte) []byte {
const (
OUTSIDE = iota
SINGLE_LINE
Expand All @@ -23,65 +22,48 @@ func decomment(data []byte) []byte {
result := make([]byte, len(data))
copy(result, data)

stateHandlers := []func(int, []byte, []byte) int{
// OUTSIDE
func(i int, data, result []byte) int {
for i := 0; i < len(result); i++ {
switch state {
case OUTSIDE:
if data[i] == '/' && i+1 < len(data) {
if data[i+1] == '/' {
state = SINGLE_LINE
result[i] = ' '
result[i+1] = ' '
return i + 1 // skip the next character
i++
} else if data[i+1] == '*' {
state = MULTI_LINE
result[i] = ' '
result[i+1] = ' '
return i + 1 // skip the next character
i++
}
} else if data[i] == '"' {
state = IN_STRING
}
return i
},
// SINGLE_LINE
func(i int, data, result []byte) int {
case SINGLE_LINE:
if data[i] == '\n' {
state = OUTSIDE
} else {
result[i] = ' '
}
return i
},
// MULTI_LINE
func(i int, data, result []byte) int {
case MULTI_LINE:
if data[i] == '*' && i+1 < len(result) && data[i+1] == '/' {
state = MULTI_LINE_ENDING
result[i] = ' '
result[i+1] = ' '
return i + 1 // skip the next character
i++
} else if result[i] != '\n' {
result[i] = ' '
}
return i
},
// MULTI_LINE_ENDING
func(i int, data, result []byte) int {
case MULTI_LINE_ENDING:
state = OUTSIDE
return i
},
// IN_STRING
func(i int, data, result []byte) int {
case IN_STRING:
if data[i] == '\\' && i+1 < len(data) {
return i + 1 // skip the escaped character
i++
} else if data[i] == '"' {
state = OUTSIDE
}
return i
},
}

for i := 0; i < len(result); i++ {
i = stateHandlers[state](i, data, result)
}
}

return result
Expand Down
4 changes: 2 additions & 2 deletions jsonc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestUnmarshalWithComments(t *testing.T) {
}
}

func TestDecommenter(t *testing.T) {
func TestStripComments(t *testing.T) {
tests := []struct {
name string
input string
Expand Down Expand Up @@ -222,7 +222,7 @@ World"}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := decomment([]byte(tt.input))
got := stripComments([]byte(tt.input))
if string(got) != tt.expected {
t.Errorf("\ntest: \"%s\"\ngot \"%s\"\nwant \"%s\"", tt.input, string(got), tt.expected)
} else {
Expand Down
48 changes: 14 additions & 34 deletions v2/jsonc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ package jsonc

import "encoding/json/v2"

// Unmarshal parses the JSON-encoded data with comment support and stores the result
// in the value pointed to by v. Options may be passed to configure the behavior of
// encoding/json/v2's Unmarshal.
func Unmarshal(data []byte, v any, opts ...json.Options) error {
processedData := decomment(data)
processedData := stripComments(data)
return json.Unmarshal(processedData, v, opts...)
}

func decomment(data []byte) []byte {
func stripComments(data []byte) []byte {
const (
OUTSIDE = iota
SINGLE_LINE
Expand All @@ -23,65 +20,48 @@ func decomment(data []byte) []byte {
result := make([]byte, len(data))
copy(result, data)

stateHandlers := []func(int, []byte, []byte) int{
// OUTSIDE
func(i int, data, result []byte) int {
for i := 0; i < len(result); i++ {
switch state {
case OUTSIDE:
if data[i] == '/' && i+1 < len(data) {
if data[i+1] == '/' {
state = SINGLE_LINE
result[i] = ' '
result[i+1] = ' '
return i + 1 // skip the next character
i++
} else if data[i+1] == '*' {
state = MULTI_LINE
result[i] = ' '
result[i+1] = ' '
return i + 1 // skip the next character
i++
}
} else if data[i] == '"' {
state = IN_STRING
}
return i
},
// SINGLE_LINE
func(i int, data, result []byte) int {
case SINGLE_LINE:
if data[i] == '\n' {
state = OUTSIDE
} else {
result[i] = ' '
}
return i
},
// MULTI_LINE
func(i int, data, result []byte) int {
case MULTI_LINE:
if data[i] == '*' && i+1 < len(result) && data[i+1] == '/' {
state = MULTI_LINE_ENDING
result[i] = ' '
result[i+1] = ' '
return i + 1 // skip the next character
i++
} else if result[i] != '\n' {
result[i] = ' '
}
return i
},
// MULTI_LINE_ENDING
func(i int, data, result []byte) int {
case MULTI_LINE_ENDING:
state = OUTSIDE
return i
},
// IN_STRING
func(i int, data, result []byte) int {
case IN_STRING:
if data[i] == '\\' && i+1 < len(data) {
return i + 1 // skip the escaped character
i++
} else if data[i] == '"' {
state = OUTSIDE
}
return i
},
}

for i := 0; i < len(result); i++ {
i = stateHandlers[state](i, data, result)
}
}

return result
Expand Down
4 changes: 2 additions & 2 deletions v2/jsonc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func TestUnmarshalWithComments(t *testing.T) {
}
}

func TestDecommenter(t *testing.T) {
func TestStripComments(t *testing.T) {
tests := []struct {
name string
input string
Expand Down Expand Up @@ -218,7 +218,7 @@ World"}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := decomment([]byte(tt.input))
got := stripComments([]byte(tt.input))
if string(got) != tt.expected {
t.Errorf("\ntest: \"%s\"\ngot \"%s\"\nwant \"%s\"", tt.input, string(got), tt.expected)
} else {
Expand Down