Skip to content

Commit 4182c47

Browse files
authored
Merge pull request #46 from jsonicjs/claude/add-mapref-option-bJI6D
2 parents cbdac59 + 8b04872 commit 4182c47

File tree

9 files changed

+1562
-11
lines changed

9 files changed

+1562
-11
lines changed

go/both_ref_test.go

Lines changed: 469 additions & 0 deletions
Large diffs are not rendered by default.

go/feature_tsv_test.go

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
package jsonic
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"path/filepath"
7+
"testing"
8+
)
9+
10+
// stripRefs recursively unwraps ListRef, MapRef, and Text back to plain
11+
// Go values ([]any, map[string]any, string) so they can be compared against
12+
// JSON-unmarshaled expected values from TSV files.
13+
func stripRefs(v any) any {
14+
if v == nil {
15+
return nil
16+
}
17+
switch val := v.(type) {
18+
case ListRef:
19+
result := make([]any, len(val.Val))
20+
for i, elem := range val.Val {
21+
result[i] = stripRefs(elem)
22+
}
23+
return result
24+
case MapRef:
25+
result := make(map[string]any)
26+
for k, elem := range val.Val {
27+
result[k] = stripRefs(elem)
28+
}
29+
return result
30+
case Text:
31+
return val.Str
32+
case map[string]any:
33+
result := make(map[string]any)
34+
for k, elem := range val {
35+
result[k] = stripRefs(elem)
36+
}
37+
return result
38+
case []any:
39+
result := make([]any, len(val))
40+
for i, elem := range val {
41+
result[i] = stripRefs(elem)
42+
}
43+
return result
44+
default:
45+
return v
46+
}
47+
}
48+
49+
// --- Standard parser TSV runner with custom options ---
50+
51+
// runParserTSV runs a standard 2-column TSV (input, expected) with a custom parser.
52+
func runParserTSV(t *testing.T, file string, j *Jsonic) {
53+
t.Helper()
54+
path := filepath.Join(specDir(), file)
55+
rows, err := loadTSV(path)
56+
if err != nil {
57+
t.Fatalf("failed to load %s: %v", file, err)
58+
}
59+
60+
for _, row := range rows {
61+
if len(row.cols) < 2 {
62+
continue
63+
}
64+
input := row.cols[0]
65+
expectedStr := row.cols[1]
66+
67+
expected, err := parseExpected(expectedStr)
68+
if err != nil {
69+
t.Errorf("line %d: failed to parse expected %q: %v", row.lineNo, expectedStr, err)
70+
continue
71+
}
72+
73+
got, err := j.Parse(preprocessEscapes(input))
74+
if err != nil {
75+
t.Errorf("line %d: Parse(%q) error: %v", row.lineNo, input, err)
76+
continue
77+
}
78+
79+
// Strip ListRef/MapRef/Text wrappers for JSON comparison.
80+
gotPlain := stripRefs(got)
81+
if !valuesEqual(gotPlain, expected) {
82+
t.Errorf("line %d: Parse(%q)\n got: %s\n expected: %s",
83+
row.lineNo, input, formatValue(gotPlain), formatValue(expected))
84+
}
85+
}
86+
}
87+
88+
// --- List-child TSV runner (3-column: input, expected_array, expected_child) ---
89+
90+
// runListChildTSV runs a list-child TSV file.
91+
func runListChildTSV(t *testing.T, file string, j *Jsonic) {
92+
t.Helper()
93+
path := filepath.Join(specDir(), file)
94+
rows, err := loadTSV(path)
95+
if err != nil {
96+
t.Fatalf("failed to load %s: %v", file, err)
97+
}
98+
99+
for _, row := range rows {
100+
if len(row.cols) < 2 {
101+
continue
102+
}
103+
input := row.cols[0]
104+
expectedArrStr := row.cols[1]
105+
expectedChildStr := ""
106+
if len(row.cols) >= 3 {
107+
expectedChildStr = row.cols[2]
108+
}
109+
110+
expectedArr, err := parseExpected(expectedArrStr)
111+
if err != nil {
112+
t.Errorf("line %d: failed to parse expected_array %q: %v", row.lineNo, expectedArrStr, err)
113+
continue
114+
}
115+
116+
var expectedChild any
117+
if expectedChildStr != "" {
118+
expectedChild, err = parseExpected(expectedChildStr)
119+
if err != nil {
120+
t.Errorf("line %d: failed to parse expected_child %q: %v", row.lineNo, expectedChildStr, err)
121+
continue
122+
}
123+
}
124+
125+
got, err := j.Parse(preprocessEscapes(input))
126+
if err != nil {
127+
t.Errorf("line %d: Parse(%q) error: %v", row.lineNo, input, err)
128+
continue
129+
}
130+
131+
lr, ok := got.(ListRef)
132+
if !ok {
133+
t.Errorf("line %d: Parse(%q) expected ListRef, got %T: %s",
134+
row.lineNo, input, got, formatValue(got))
135+
continue
136+
}
137+
138+
// Compare Val (strip inner refs for JSON comparison).
139+
gotArr := stripRefs(lr.Val)
140+
if !valuesEqual(gotArr, expectedArr) {
141+
t.Errorf("line %d: Parse(%q) Val\n got: %s\n expected: %s",
142+
row.lineNo, input, formatValue(gotArr), formatValue(expectedArr))
143+
}
144+
145+
// Compare Child.
146+
gotChild := stripRefs(lr.Child)
147+
if !valuesEqual(gotChild, expectedChild) {
148+
t.Errorf("line %d: Parse(%q) Child\n got: %s\n expected: %s",
149+
row.lineNo, input, formatValue(gotChild), formatValue(expectedChild))
150+
}
151+
}
152+
}
153+
154+
// --- feature-list-child.tsv ---
155+
156+
func TestTSVFeatureListChild(t *testing.T) {
157+
j := Make(Options{List: &ListOptions{Child: boolPtr(true)}})
158+
runListChildTSV(t, "feature-list-child.tsv", j)
159+
}
160+
161+
// --- feature-list-child-deep.tsv ---
162+
163+
func TestTSVFeatureListChildDeep(t *testing.T) {
164+
j := Make(Options{List: &ListOptions{Child: boolPtr(true)}})
165+
runListChildTSV(t, "feature-list-child-deep.tsv", j)
166+
}
167+
168+
// --- feature-list-child-pair.tsv ---
169+
170+
func TestTSVFeatureListChildPair(t *testing.T) {
171+
j := Make(Options{List: &ListOptions{
172+
Child: boolPtr(true),
173+
Pair: boolPtr(true),
174+
}})
175+
runListChildTSV(t, "feature-list-child-pair.tsv", j)
176+
}
177+
178+
// --- feature-list-child-pair-deep.tsv ---
179+
180+
func TestTSVFeatureListChildPairDeep(t *testing.T) {
181+
j := Make(Options{List: &ListOptions{
182+
Child: boolPtr(true),
183+
Pair: boolPtr(true),
184+
}})
185+
runListChildTSV(t, "feature-list-child-pair-deep.tsv", j)
186+
}
187+
188+
// --- feature-list-pair.tsv ---
189+
190+
func TestTSVFeatureListPair(t *testing.T) {
191+
j := Make(Options{List: &ListOptions{Pair: boolPtr(true)}})
192+
runParserTSV(t, "feature-list-pair.tsv", j)
193+
}
194+
195+
// --- feature-map-child.tsv ---
196+
197+
func TestTSVFeatureMapChild(t *testing.T) {
198+
j := Make(Options{Map: &MapOptions{Child: boolPtr(true)}})
199+
runParserTSV(t, "feature-map-child.tsv", j)
200+
}
201+
202+
// --- feature-map-child-deep.tsv ---
203+
204+
func TestTSVFeatureMapChildDeep(t *testing.T) {
205+
// Deep tests combine map.child with list.child for nested structures.
206+
j := Make(Options{
207+
Map: &MapOptions{Child: boolPtr(true)},
208+
List: &ListOptions{Child: boolPtr(true)},
209+
})
210+
runParserTSV(t, "feature-map-child-deep.tsv", j)
211+
}
212+
213+
// --- Verify formatValue handles ListRef for debugging ---
214+
215+
func TestStripRefsBasic(t *testing.T) {
216+
lr := ListRef{
217+
Val: []any{MapRef{Val: map[string]any{"a": 1.0}, Implicit: false}, "hello"},
218+
Implicit: true,
219+
Child: Text{Str: "world", Quote: `"`},
220+
}
221+
got := stripRefs(lr)
222+
expected := []any{map[string]any{"a": 1.0}, "hello"}
223+
if !valuesEqual(got, expected) {
224+
b, _ := json.Marshal(got)
225+
t.Errorf("stripRefs: got %s, expected %s", string(b), fmt.Sprintf("%v", expected))
226+
}
227+
}

0 commit comments

Comments
 (0)