mirror of
https://github.com/therootcompany/golib.git
synced 2026-03-13 12:27:59 +00:00
Add tools/jsontypes library and tools/jsontypes/cmd/jsonpaths CLI. Given a JSON sample (file, URL, or stdin), walks the structure, detects maps vs structs, infers optional fields from multiple instances, and produces typed definitions. Output formats (--format): - json-paths: flat type path notation (default) - go: struct definitions with json tags and union support - typescript: interfaces with optional/nullable fields - jsdoc: @typedef annotations - zod: validation schemas with type inference - python: TypedDict classes - sql: CREATE TABLE with FK relationships - json-schema: draft 2020-12 - json-typedef: RFC 8927 Features: - Interactive prompts for ambiguous structure (map vs struct, same vs different types), with --anonymous mode for non-interactive use - Answer replay: saves prompt answers to .answers files for iterative refinement - URL fetching with local caching and sensitive param stripping - Curl-like auth: -H, --bearer, --user, --cookie, --cookie-jar - Discriminated union support with sealed interfaces, unique-field probing, and CHANGE ME comments for type/kind discriminators - Extensive round-trip compilation tests for generated Go code
81 lines
1.6 KiB
Go
81 lines
1.6 KiB
Go
package jsontypes
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// generatePython converts formatted flat paths into Python TypedDict definitions.
|
|
func GeneratePython(paths []string) string {
|
|
types, _ := buildGoTypes(paths)
|
|
if len(types) == 0 {
|
|
return ""
|
|
}
|
|
|
|
hasOptional := false
|
|
for _, t := range types {
|
|
for _, f := range t.fields {
|
|
if f.optional {
|
|
hasOptional = true
|
|
break
|
|
}
|
|
}
|
|
if hasOptional {
|
|
break
|
|
}
|
|
}
|
|
|
|
var buf strings.Builder
|
|
buf.WriteString("from __future__ import annotations\n\n")
|
|
if hasOptional {
|
|
buf.WriteString("from typing import NotRequired, TypedDict\n")
|
|
} else {
|
|
buf.WriteString("from typing import TypedDict\n")
|
|
}
|
|
|
|
// Emit in reverse so referenced types come first.
|
|
for i := len(types) - 1; i >= 0; i-- {
|
|
t := types[i]
|
|
buf.WriteString(fmt.Sprintf("\n\nclass %s(TypedDict):\n", t.name))
|
|
if len(t.fields) == 0 {
|
|
buf.WriteString(" pass\n")
|
|
continue
|
|
}
|
|
for _, f := range t.fields {
|
|
pyType := goTypeToPython(f.goType)
|
|
if f.optional {
|
|
buf.WriteString(fmt.Sprintf(" %s: NotRequired[%s | None]\n", f.jsonName, pyType))
|
|
} else {
|
|
buf.WriteString(fmt.Sprintf(" %s: %s\n", f.jsonName, pyType))
|
|
}
|
|
}
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
func goTypeToPython(goTyp string) string {
|
|
goTyp = strings.TrimPrefix(goTyp, "*")
|
|
|
|
if strings.HasPrefix(goTyp, "[]") {
|
|
return "list[" + goTypeToPython(goTyp[2:]) + "]"
|
|
}
|
|
if strings.HasPrefix(goTyp, "map[string]") {
|
|
return "dict[str, " + goTypeToPython(goTyp[11:]) + "]"
|
|
}
|
|
|
|
switch goTyp {
|
|
case "string":
|
|
return "str"
|
|
case "int64":
|
|
return "int"
|
|
case "float64":
|
|
return "float"
|
|
case "bool":
|
|
return "bool"
|
|
case "any":
|
|
return "object"
|
|
default:
|
|
return goTyp
|
|
}
|
|
}
|