mirror of
https://github.com/therootcompany/golib.git
synced 2026-11-04 06:55:00 +00:00
fix(jsontypes): handle sample format in Go struct generator and path parser
- parsePath now uses brace-depth matching to correctly parse nested JSON
in sample type intros like {Root:{"name":"pikachu","id":25}}
- cleanTypeName strips sample data from type intros (Root:{...} → Root)
- isPrimitiveType recognizes sample leaf values (quoted strings, numbers)
- primitiveToGo maps sample values to proper Go types (string, int64, etc.)
- All three pipeline tests pass: raw→Go, coalesced→Go, and sample→Go
This commit is contained in:
parent
2129157f8d
commit
e7c6d25bed
@ -42,9 +42,9 @@ func parsePath(path string) []segment {
|
||||
i = i + end + 1
|
||||
}
|
||||
|
||||
// Type: read {Type}
|
||||
// Type: read {Type} (handling nested braces in sample data)
|
||||
if i < len(path) && path[i] == '{' {
|
||||
end := strings.IndexByte(path[i:], '}')
|
||||
end := findMatchingBrace(path[i:])
|
||||
if end < 0 {
|
||||
break
|
||||
}
|
||||
@ -290,6 +290,24 @@ func buildBareWithParent(segs []segment, parentIdx int) string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// findMatchingBrace returns the index of the closing '}' that matches the
|
||||
// opening '{' at s[0], respecting nested braces. Returns -1 if not found.
|
||||
func findMatchingBrace(s string) int {
|
||||
depth := 0
|
||||
for i, c := range s {
|
||||
switch c {
|
||||
case '{':
|
||||
depth++
|
||||
case '}':
|
||||
depth--
|
||||
if depth == 0 {
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// buildBare builds a path string without any type annotations, for sorting.
|
||||
func buildBare(segs []segment) string {
|
||||
var buf strings.Builder
|
||||
|
||||
@ -509,18 +509,37 @@ func primitiveToGo(typ string) string {
|
||||
return "int64"
|
||||
case "float":
|
||||
return "float64"
|
||||
case "bool":
|
||||
case "bool", "true", "false":
|
||||
return "bool"
|
||||
case "null", "unknown":
|
||||
case "null", "unknown", "empty", "undefined":
|
||||
return "any"
|
||||
default:
|
||||
// Sample format: quoted strings → string, numbers → numeric type.
|
||||
if len(typ) > 0 && typ[0] == '"' {
|
||||
return "string"
|
||||
}
|
||||
if len(typ) > 0 && (typ[0] >= '0' && typ[0] <= '9' || typ[0] == '-') {
|
||||
if strings.ContainsAny(typ, ".eE") {
|
||||
return "float64"
|
||||
}
|
||||
return "int64"
|
||||
}
|
||||
return typ
|
||||
}
|
||||
}
|
||||
|
||||
func isPrimitiveType(typ string) bool {
|
||||
switch typ {
|
||||
case "string", "int", "float", "bool", "null", "unknown", "any":
|
||||
case "string", "int", "float", "bool", "null", "unknown", "any",
|
||||
"empty", "undefined":
|
||||
return true
|
||||
}
|
||||
// Sample format: quoted strings like `"pikachu"`, numbers like `25` or `3.14`,
|
||||
// booleans `true`/`false` are leaf values, not type names.
|
||||
if len(typ) > 0 && typ[0] == '"' {
|
||||
return true
|
||||
}
|
||||
if len(typ) > 0 && (typ[0] >= '0' && typ[0] <= '9' || typ[0] == '-') {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@ -534,7 +553,16 @@ func makePointer(typ string) string {
|
||||
}
|
||||
|
||||
func cleanTypeName(typ string) string {
|
||||
return strings.TrimSuffix(typ, "?")
|
||||
typ = strings.TrimSuffix(typ, "?")
|
||||
// Strip sample data from type intros like "Root:{...}" → "Root".
|
||||
if idx := strings.Index(typ, ":"); idx > 0 {
|
||||
candidate := typ[:idx]
|
||||
// A type name starts with uppercase.
|
||||
if len(candidate) > 0 && candidate[0] >= 'A' && candidate[0] <= 'Z' {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
func sortGoFields(fields []goField) {
|
||||
|
||||
76
tools/jsontypes/pipeline_test.go
Normal file
76
tools/jsontypes/pipeline_test.go
Normal file
@ -0,0 +1,76 @@
|
||||
package jsontypes
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPipelineEndToEnd(t *testing.T) {
|
||||
// Test the full pipeline: JSON → RawPaths → Coalesce → GenerateGoStructs.
|
||||
data := loadJSONFixture(t, "testdata/swapi-luke.json")
|
||||
raw := RawPaths(data)
|
||||
coalesced := Coalesce(raw)
|
||||
|
||||
// The coalesced output should be directly usable by the Go generator.
|
||||
goCode := GenerateGoStructs(coalesced)
|
||||
|
||||
if goCode == "" {
|
||||
t.Fatal("GenerateGoStructs returned empty output")
|
||||
}
|
||||
|
||||
// Should produce a Root struct with expected fields.
|
||||
if !strings.Contains(goCode, "type Root struct") {
|
||||
t.Error("missing Root struct")
|
||||
}
|
||||
if !strings.Contains(goCode, "`json:\"name\"`") {
|
||||
t.Error("missing name field")
|
||||
}
|
||||
if !strings.Contains(goCode, "`json:\"height\"`") {
|
||||
t.Error("missing height field")
|
||||
}
|
||||
|
||||
t.Logf("Go output:\n%s", goCode)
|
||||
}
|
||||
|
||||
func TestPipelinePokemon(t *testing.T) {
|
||||
data := loadJSONFixture(t, "testdata/pokemon-pikachu.json")
|
||||
raw := RawPaths(data)
|
||||
coalesced := Coalesce(raw)
|
||||
|
||||
goCode := GenerateGoStructs(coalesced)
|
||||
|
||||
if !strings.Contains(goCode, "type Root struct") {
|
||||
t.Error("missing Root struct")
|
||||
}
|
||||
if !strings.Contains(goCode, "type AbilitiesItem struct") {
|
||||
t.Error("missing AbilitiesItem struct")
|
||||
}
|
||||
if !strings.Contains(goCode, "type NameUrl struct") {
|
||||
t.Error("missing NameUrl struct")
|
||||
}
|
||||
|
||||
// NameUrl should be used as a field type in multiple structs.
|
||||
nameUrlCount := strings.Count(goCode, "NameUrl")
|
||||
if nameUrlCount < 3 {
|
||||
t.Errorf("NameUrl used only %d times (expected many references)", nameUrlCount)
|
||||
}
|
||||
|
||||
t.Logf("Go output (%d bytes, NameUrl used %d times):\n%s", len(goCode), nameUrlCount, goCode)
|
||||
}
|
||||
|
||||
func TestPipelineSampleToGo(t *testing.T) {
|
||||
// Sample output should also work with the Go generator.
|
||||
data := loadJSONFixture(t, "testdata/swapi-planets.json")
|
||||
sample := SampleWithConfig(data, SampleConfig{MaxStringLen: 20})
|
||||
|
||||
goCode := GenerateGoStructs(sample)
|
||||
|
||||
if !strings.Contains(goCode, "type Root struct") {
|
||||
t.Error("missing Root struct")
|
||||
}
|
||||
if !strings.Contains(goCode, "type ResultsItem struct") {
|
||||
t.Error("missing ResultsItem struct")
|
||||
}
|
||||
|
||||
t.Logf("Go output:\n%s", goCode)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user