From 1454fb545daa423b90e46c4ec14e061ef62d78b8 Mon Sep 17 00:00:00 2001 From: fpatron Date: Sun, 1 Mar 2026 08:54:20 -0700 Subject: [PATCH 1/2] Closure dispatch table replaced with a switch state statement --- jsonc.go | 46 ++++++++++++++-------------------------------- jsonc_test.go | 4 ++-- v2/jsonc.go | 48 ++++++++++++++---------------------------------- 3 files changed, 30 insertions(+), 68 deletions(-) diff --git a/jsonc.go b/jsonc.go index 25ed607..e9cfe69 100644 --- a/jsonc.go +++ b/jsonc.go @@ -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 @@ -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 diff --git a/jsonc_test.go b/jsonc_test.go index 10afd71..fa0bbe7 100644 --- a/jsonc_test.go +++ b/jsonc_test.go @@ -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 @@ -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 { diff --git a/v2/jsonc.go b/v2/jsonc.go index 783b9cf..cc807de 100644 --- a/v2/jsonc.go +++ b/v2/jsonc.go @@ -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 @@ -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 From 8b35238a1d63453c8a8d6907b91e2c63278d2af4 Mon Sep 17 00:00:00 2001 From: fpatron Date: Sun, 1 Mar 2026 08:57:05 -0700 Subject: [PATCH 2/2] fix v2 tests --- v2/jsonc_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/v2/jsonc_test.go b/v2/jsonc_test.go index 0a439c7..d6e4be7 100644 --- a/v2/jsonc_test.go +++ b/v2/jsonc_test.go @@ -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 @@ -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 {