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
116 lines
2.6 KiB
Go
116 lines
2.6 KiB
Go
package jsontypes
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
)
|
|
|
|
// generateJSONSchema converts formatted flat paths into a JSON Schema (draft 2020-12) document.
|
|
func GenerateJSONSchema(paths []string) string {
|
|
types, _ := buildGoTypes(paths)
|
|
|
|
typeMap := make(map[string]goType)
|
|
for _, t := range types {
|
|
typeMap[t.name] = t
|
|
}
|
|
|
|
if len(types) == 0 {
|
|
return "{}\n"
|
|
}
|
|
|
|
root := types[0]
|
|
defs := make(map[string]any)
|
|
result := structToJSONSchema(root, typeMap, defs)
|
|
result["$schema"] = "https://json-schema.org/draft/2020-12/schema"
|
|
|
|
if len(defs) > 0 {
|
|
result["$defs"] = defs
|
|
}
|
|
|
|
data, _ := json.MarshalIndent(result, "", " ")
|
|
return string(data) + "\n"
|
|
}
|
|
|
|
func structToJSONSchema(t goType, typeMap map[string]goType, defs map[string]any) map[string]any {
|
|
props := make(map[string]any)
|
|
var required []string
|
|
|
|
for _, f := range t.fields {
|
|
schema := goTypeToJSONSchema(f.goType, f.optional, typeMap, defs)
|
|
props[f.jsonName] = schema
|
|
if !f.optional {
|
|
required = append(required, f.jsonName)
|
|
}
|
|
}
|
|
|
|
result := map[string]any{
|
|
"type": "object",
|
|
"properties": props,
|
|
}
|
|
if len(required) > 0 {
|
|
result["required"] = required
|
|
}
|
|
return result
|
|
}
|
|
|
|
func goTypeToJSONSchema(goTyp string, nullable bool, typeMap map[string]goType, defs map[string]any) map[string]any {
|
|
result := goTypeToJSONSchemaInner(goTyp, typeMap, defs)
|
|
if nullable {
|
|
// JSON Schema nullable: anyOf with null
|
|
return map[string]any{
|
|
"anyOf": []any{
|
|
result,
|
|
map[string]any{"type": "null"},
|
|
},
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func goTypeToJSONSchemaInner(goTyp string, typeMap map[string]goType, defs map[string]any) map[string]any {
|
|
goTyp = strings.TrimPrefix(goTyp, "*")
|
|
|
|
// Slice
|
|
if strings.HasPrefix(goTyp, "[]") {
|
|
elemType := goTyp[2:]
|
|
return map[string]any{
|
|
"type": "array",
|
|
"items": goTypeToJSONSchemaInner(elemType, typeMap, defs),
|
|
}
|
|
}
|
|
|
|
// Map
|
|
if strings.HasPrefix(goTyp, "map[string]") {
|
|
valType := goTyp[11:]
|
|
return map[string]any{
|
|
"type": "object",
|
|
"additionalProperties": goTypeToJSONSchemaInner(valType, typeMap, defs),
|
|
}
|
|
}
|
|
|
|
// Primitives
|
|
switch goTyp {
|
|
case "string":
|
|
return map[string]any{"type": "string"}
|
|
case "int64":
|
|
return map[string]any{"type": "integer"}
|
|
case "float64":
|
|
return map[string]any{"type": "number"}
|
|
case "bool":
|
|
return map[string]any{"type": "boolean"}
|
|
case "any":
|
|
return map[string]any{}
|
|
}
|
|
|
|
// Named struct — emit as $ref, add to $defs
|
|
if t, ok := typeMap[goTyp]; ok {
|
|
if _, exists := defs[goTyp]; !exists {
|
|
defs[goTyp] = nil // placeholder
|
|
defs[goTyp] = structToJSONSchema(t, typeMap, defs)
|
|
}
|
|
return map[string]any{"$ref": "#/$defs/" + goTyp}
|
|
}
|
|
|
|
return map[string]any{}
|
|
}
|