feat(jsontypes): short raw paths — type name only on intro line

Type names now appear once on the intro line, and subsequent fields
use the bare path without repeating the type. Root no longer has a
leading dot.

Before: .{Root0}.rooms[]{RoomsItem1}._timing{Timing2}.account_ms{int}
After:  .rooms[]._timing.account_ms{int}

The type intro line declares the type, making the path structure
cleaner and more readable while remaining lossless.
This commit is contained in:
AJ ONeal 2026-03-07 23:23:34 -07:00
parent 69d757bc69
commit a346049cc4
No known key found for this signature in database
2 changed files with 27 additions and 30 deletions

View File

@ -16,7 +16,7 @@ import (
// integers and floats are distinguishable.
func RawPaths(v any) []string {
w := &rawWalker{}
w.walk(".", v)
w.walk("", v)
return w.paths
}
@ -31,20 +31,6 @@ func (w *rawWalker) emit(path string) {
func (w *rawWalker) nextName(path string) string {
name := inferRawTypeName(path)
// If the inferred name matches a parent type in the path, prefix with
// the parent to disambiguate. E.g., a "room" field under {Room1} would
// infer "Room" — prefix it to "RoomRoom" so the reader knows it's the
// child type, not the parent.
parent := parentTypeName(path)
if parent != "" {
// Strip trailing digits from parent for comparison.
parentBase := strings.TrimRight(parent, "0123456789")
if parentBase == name {
name = parentBase + name
}
}
id := w.counter
w.counter++
return fmt.Sprintf("%s%d", name, id)
@ -101,11 +87,12 @@ func (w *rawWalker) walkMap(prefix string, obj map[string]any) {
func (w *rawWalker) walkStruct(prefix string, instances []map[string]any) {
merged := mergeObjects(instances)
typeName := w.nextName(prefix)
typePrefix := prefix + "{" + typeName + "}"
// Emit type intro.
w.emit(typePrefix)
// Emit type intro with the type name.
w.emit(prefix + "{" + typeName + "}")
// Fields use the bare prefix (without the type name) since
// the type was already declared on the intro line above.
keys := sortedKeys(merged)
for _, k := range keys {
// Collect all values for this field across instances.
@ -117,7 +104,7 @@ func (w *rawWalker) walkStruct(prefix string, instances []map[string]any) {
fieldVals = append(fieldVals, nil)
}
}
w.walkCollection(typePrefix+"."+k, fieldVals)
w.walkCollection(joinPath(prefix, k), fieldVals)
}
}
@ -235,7 +222,7 @@ func (w *rawWalker) walkCollection(prefix string, values []any) {
// Collection elements get "<Name>Item" (e.g., friends[] → FriendsItem).
// Direct fields use PascalCase as-is (e.g., address → Address).
func inferRawTypeName(path string) string {
if path == "." {
if path == "" {
return "Root"
}
@ -282,3 +269,10 @@ func inferRawTypeName(path string) string {
return name
}
// joinPath appends a field name to a path prefix with a dot separator.
func joinPath(prefix, field string) string {
if prefix == "" {
return "." + field
}
return prefix + "." + field
}

View File

@ -20,11 +20,12 @@ func TestRawPathsFixtures(t *testing.T) {
name: "pokemon",
file: "testdata/pokemon-pikachu.json",
mustContain: []string{
".{Root0}.name{string}",
".{Root0}.abilities[]{AbilitiesItem",
".{Root0}.stats[]{StatsItem",
".{Root0}.types[]{TypesItem",
".{Root0}.sprites{Sprite",
"{Root0}",
".name{string}",
".abilities[]{AbilitiesItem",
".stats[]{StatsItem",
".types[]{TypesItem",
".sprites{Sprite",
},
maxLines: 250,
},
@ -32,9 +33,10 @@ func TestRawPathsFixtures(t *testing.T) {
name: "swapi_person",
file: "testdata/swapi-luke.json",
mustContain: []string{
".{Root0}.name{string}",
".{Root0}.height{string}",
".{Root0}.films[]{string}",
"{Root0}",
".name{string}",
".height{string}",
".films[]{string}",
},
maxLines: 30,
},
@ -42,8 +44,9 @@ func TestRawPathsFixtures(t *testing.T) {
name: "swapi_planets",
file: "testdata/swapi-planets.json",
mustContain: []string{
".{Root0}.count{int}",
".{Root0}.results[]{ResultsItem",
"{Root0}",
".count{int}",
".results[]{ResultsItem",
"climate{string}",
"population{string}",
},