├── fromjsd.go ├── jsondef.go ├── readme.md ├── jsonschema.go └── jsonschema-extras.go /fromjsd.go: -------------------------------------------------------------------------------- 1 | // Generates Go `struct` (et al) type definitions (ready to `json.Unmarshal` into) from a JSON Schema definition. 2 | // 3 | // Caution: contains a few strategically placed `panic`s for edge cases not-yet-considered/implemented/handled/needed. If it `panic`s for your JSON Schema, report! 4 | // 5 | // - Use it like [this main.go](https://github.com/metaleap/zentient/blob/master/cmd/zentient-dbg-vsc-genprotocol/main.go) does.. 6 | // 7 | // - ..to turn a JSON Schema [like this](https://github.com/Microsoft/vscode-debugadapter-node/blob/master/debugProtocol.json).. 8 | // 9 | // - ..into a monster `.go` package of `struct` (et al) type-defs [like this](https://github.com/metaleap/zentient/blob/master/dbg/vsc/protocol/protocol.go) 10 | package fromjsd 11 | 12 | import ( 13 | "github.com/go-leap/str" 14 | ) 15 | 16 | var ( 17 | // Will be appended to the resulting generated package doc-comment summary 18 | GoPkgDesc = "Package codegen'd via github.com/metaleap/go-fromjsonschema" 19 | 20 | // Default JSON-to-Go type mappings, `number` could be tweaked to `float64` depending on the use-case at hand 21 | TypeMapping = map[string]string{ 22 | "boolean": "bool", 23 | "number": "int64", 24 | "integer": "int", 25 | "string": "string", 26 | "null": "interface{/*nil*/}", 27 | "array": "[]interface{}", 28 | "object": "map[string]interface{}", 29 | } 30 | ) 31 | 32 | func unRef(r string) string { 33 | const l = 14 // len("#/definitions/") 34 | return r[l:] 35 | } 36 | 37 | func tabChars(n int) string { 38 | return ustr.Times("\t", n) 39 | } 40 | 41 | func writeDesc(ind int, b *ustr.Buf, desc string) { 42 | tabchars := tabChars(ind) 43 | if desclns := ustr.Split(ustr.Trim(desc), "\n"); len(desclns) > 0 { 44 | for _, dln := range desclns { 45 | b.Writelnf("%s// %s", tabchars, dln) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /jsondef.go: -------------------------------------------------------------------------------- 1 | package fromjsd 2 | 3 | import ( 4 | "github.com/go-leap/str" 5 | ) 6 | 7 | // Represents a top-level type definition, or a property definition, 8 | // a type-reference, an embedded anonymous `struct`/`object` type 9 | // definition, or an `array`/`map` element type definition.. 10 | type JsonDef struct { 11 | base string 12 | Desc string `json:"description,omitempty"` // top-level defs 13 | AllOf []*JsonDef `json:"allOf,omitempty"` // tld 14 | Props map[string]*JsonDef `json:"properties,omitempty"` // tld 15 | Type []string `json:"type,omitempty"` // tld 16 | Req []string `json:"required,omitempty"` // tld 17 | Enum []string `json:"enum,omitempty"` // tld 18 | Enum_ []string `json:"_enum,omitempty"` // prop defs 19 | TMap *JsonDef `json:"additionalProperties,omitempty"` // pd 20 | TArr *JsonDef `json:"items,omitempty"` // pd 21 | Ref string `json:"$ref,omitempty"` // pd or base from allof[0] 22 | } 23 | 24 | func (me *JsonDef) EnsureProps(propNamesAndTypes map[string]string) { 25 | if me.Props == nil { 26 | // me.Props = map[string]*JsonDef{} 27 | panic("EnsureProps: Props was nil and so this likely isn't supposed to have any") 28 | } 29 | for pname, ptype := range propNamesAndTypes { 30 | if pdef, ok := me.Props[pname]; pdef == nil || !ok { 31 | pdef = &JsonDef{Type: []string{ptype}, Desc: pname} 32 | me.Props[pname] = pdef 33 | } else { 34 | panic("EnsureProps: property `" + pname + "` exists in the original schema now, remove it from this call.") 35 | } 36 | } 37 | } 38 | 39 | func (me *JsonDef) genStructFields(ind int, b *ustr.Buf) { 40 | tabchars := tabChars(ind) 41 | for pname, pdef := range me.Props { 42 | if len(pdef.AllOf) > 0 { 43 | panic(pname) 44 | } 45 | ftname := pdef.genTypeName(ind) 46 | gtname := me.propNameToFieldName(pname) 47 | b.Writeln("") 48 | pdef.updateDescBasedOnStrEnumVals() 49 | writeDesc(ind, b, pdef.Desc) 50 | omitempty := ",omitempty" 51 | if ustr.In(pname, me.Req...) { 52 | omitempty = "" 53 | } 54 | b.Writelnf("%s%s %s `json:\"%s%s\"`", tabchars, gtname, ftname, pname, omitempty) 55 | } 56 | } 57 | 58 | func (me *JsonDef) genTypeName(ind int) (ftname string) { 59 | ftname = "interface{}" 60 | if me != nil { 61 | if len(me.Ref) > 0 { 62 | ftname = unRef(me.Ref) 63 | } else if len(me.Type) > 1 { 64 | me.Desc += "\n\nPOSSIBLE TYPES:" 65 | for _, jtn := range me.Type { 66 | me.Desc += "\n- `" + TypeMapping[jtn] + "` (for JSON `" + jtn + "`s)" 67 | } 68 | } else if len(me.Type) > 0 { 69 | switch me.Type[0] { 70 | case "object": 71 | if me.Props != nil && len(me.Props) > 0 { 72 | var b ustr.Buf 73 | me.genStructFields(ind+1, &b) 74 | ftname = "struct {\n" + b.String() + "\n" + tabChars(ind) + "}" 75 | } else if me.TMap != nil { 76 | ftname = "map[string]" + me.TMap.genTypeName(ind) 77 | } else { 78 | ftname = TypeMapping["object"] 79 | } 80 | case "array": 81 | ftname = "[]" + me.TArr.genTypeName(ind) 82 | default: 83 | if tn, ok := TypeMapping[me.Type[0]]; ok { 84 | ftname = tn 85 | } else { 86 | panic(me.Type[0]) 87 | } 88 | } 89 | } 90 | } 91 | return 92 | } 93 | 94 | func (me *JsonDef) propNameToFieldName(pname string) (fname string) { 95 | for ustr.Pref(pname, "_") { 96 | pname = pname[1:] + "_" 97 | } 98 | if fname = ustr.Case(pname, 0, true); fname == me.base { 99 | fname += "_" 100 | } 101 | return 102 | } 103 | 104 | func (me *JsonDef) updateDescBasedOnStrEnumVals() { 105 | if len(me.Type) > 0 && me.Type[0] == "string" { 106 | en := me.Enum_ 107 | if len(en) == 0 { 108 | en = me.Enum 109 | } 110 | if len(en) > 0 { 111 | me.Desc += "\n\nPOSSIBLE VALUES: `" + ustr.Join(en, "`, `") + "`" 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # fromjsd 2 | -- 3 | import "github.com/metaleap/go-fromjsonschema" 4 | 5 | Generates Go `struct` (et al) type definitions (ready to `json.Unmarshal` into) 6 | from a JSON Schema definition. 7 | 8 | Caution: contains a few strategically placed `panic`s for edge cases 9 | not-yet-considered/implemented/handled/needed. If it `panic`s for your JSON 10 | Schema, report! 11 | 12 | - Use it like [this 13 | main.go](https://github.com/metaleap/zentient/blob/master/cmd/zentient-dbg-vsc-genprotocol/main.go) 14 | does.. 15 | 16 | - ..to turn a JSON Schema [like 17 | this](https://github.com/Microsoft/vscode-debugadapter-node/blob/master/debugProtocol.json).. 18 | 19 | - ..into a monster `.go` package of `struct` (et al) type-defs [like 20 | this](https://github.com/metaleap/zentient/blob/master/dbg/vsc/protocol/protocol.go) 21 | 22 | ## Usage 23 | 24 | ```go 25 | var ( 26 | // Will be appended to the resulting generated package doc-comment summary 27 | GoPkgDesc = "Package codegen'd via github.com/metaleap/go-fromjsonschema" 28 | 29 | // Default JSON-to-Go type mappings, `number` could be tweaked to `float64` depending on the use-case at hand 30 | TypeMapping = map[string]string{ 31 | "boolean": "bool", 32 | "number": "int64", 33 | "integer": "int", 34 | "string": "string", 35 | "null": "interface{/*nil*/}", 36 | "array": "[]interface{}", 37 | "object": "map[string]interface{}", 38 | } 39 | ) 40 | ``` 41 | 42 | #### type JsonDef 43 | 44 | ```go 45 | type JsonDef struct { 46 | Desc string `json:"description,omitempty"` // top-level defs 47 | AllOf []*JsonDef `json:"allOf,omitempty"` // tld 48 | Props map[string]*JsonDef `json:"properties,omitempty"` // tld 49 | Type []string `json:"type,omitempty"` // tld 50 | Req []string `json:"required,omitempty"` // tld 51 | Enum []string `json:"enum,omitempty"` // tld 52 | Enum_ []string `json:"_enum,omitempty"` // prop defs 53 | TMap *JsonDef `json:"additionalProperties,omitempty"` // pd 54 | TArr *JsonDef `json:"items,omitempty"` // pd 55 | Ref string `json:"$ref,omitempty"` // pd or base from allof[0] 56 | } 57 | ``` 58 | 59 | 60 | Represents a top-level type definition, or a property definition, 61 | a type-reference, an embedded anonymous `struct`/`object` type definition, or an 62 | `array`/`map` element type definition.. 63 | 64 | #### func (*JsonDef) EnsureProps 65 | 66 | ```go 67 | func (me *JsonDef) EnsureProps(propNamesAndTypes map[string]string) 68 | ``` 69 | 70 | #### type JsonSchema 71 | 72 | ```go 73 | type JsonSchema struct { 74 | // Something like `http://json-schema.org/draft-04/schema#` 75 | Schema string `json:"$schema,omitempty"` 76 | 77 | Title string `json:"title,omitempty"` 78 | 79 | Desc string `json:"description,omitempty"` 80 | 81 | // Ignored, assuming `["object"]` (prior to unmarshaling a JSON Schhema, we transform all `\"type\": \"foo\"` into \"type\": [\"foo\"] for now) 82 | Type []string `json:"type,omitempty"` 83 | 84 | // The JSON Schema's type definitions 85 | Defs map[string]*JsonDef `json:"definitions,omitempty"` 86 | } 87 | ``` 88 | 89 | Top-level declarations of a JSON Schema 90 | 91 | #### func NewJsonSchema 92 | 93 | ```go 94 | func NewJsonSchema(jsonSchemaDefSrc string) (*JsonSchema, error) 95 | ``` 96 | Obtains from the given JSON Schema source a `*JsonSchema` that can be passed to 97 | `Generate`. `err` is `nil` unless unmarshaling the specified `jsonSchemaDefSrc` 98 | failed. 99 | 100 | #### func (*JsonSchema) ForceCopyProps 101 | 102 | ```go 103 | func (me *JsonSchema) ForceCopyProps(fromBaseTypeName string, toBaseTypeName string, pnames ...string) 104 | ``` 105 | 106 | #### func (*JsonSchema) Generate 107 | 108 | ```go 109 | func (me *JsonSchema) Generate(goPkgName string, generateDecodeHelpersForBaseTypeNames map[string]string, generateHandlinScaffoldsForBaseTypes map[string]string, generateCtorsForBaseTypes ...string) string 110 | ``` 111 | Generate a Go package source with type-defs representing the `Defs` in `jsd` 112 | (typically obtained via `NewJsonSchema`). 113 | 114 | Arguments beyond `goPkgName` generate further code beyond the type-defs: these 115 | may all be `nil`/zeroed, or if one "sounds like what you need", check the source 116 | for how they're handled otherwise =) 117 | -------------------------------------------------------------------------------- /jsonschema.go: -------------------------------------------------------------------------------- 1 | package fromjsd 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/go-leap/str" 7 | ) 8 | 9 | // Top-level declarations of a JSON Schema 10 | type JsonSchema struct { 11 | // Something like `http://json-schema.org/draft-04/schema#` 12 | Schema string `json:"$schema,omitempty"` 13 | 14 | Title string `json:"title,omitempty"` 15 | 16 | Desc string `json:"description,omitempty"` 17 | 18 | // Ignored, assuming `["object"]` (prior to unmarshaling a JSON Schhema, we transform all `\"type\": \"foo\"` into \"type\": [\"foo\"] for now) 19 | Type []string `json:"type,omitempty"` 20 | 21 | // The JSON Schema's type definitions 22 | Defs map[string]*JsonDef `json:"definitions,omitempty"` 23 | } 24 | 25 | // Obtains from the given JSON Schema source a `*JsonSchema` that can be passed to `Generate`. 26 | // `err` is `nil` unless unmarshaling the specified `jsonSchemaDefSrc` failed. 27 | func NewJsonSchema(jsonSchemaDefSrc string) (*JsonSchema, error) { 28 | for i := ustr.Pos(jsonSchemaDefSrc, "\"type\": \""); i >= 0; i = ustr.Pos(jsonSchemaDefSrc, "\"type\": \"") { 29 | j := ustr.Pos(jsonSchemaDefSrc[i+9:], "\"") 30 | tname := jsonSchemaDefSrc[i+9:][:j] 31 | jsonSchemaDefSrc = jsonSchemaDefSrc[:i] + "\"type\": [\"" + tname + "\"]" + jsonSchemaDefSrc[i+9+j+1:] 32 | } 33 | var jdefs JsonSchema 34 | if err := json.Unmarshal([]byte(jsonSchemaDefSrc), &jdefs); err != nil { 35 | return nil, err 36 | } 37 | topleveldefs := map[string]*JsonDef{} 38 | for tname, jdef := range jdefs.Defs { 39 | if len(jdef.AllOf) == 0 { 40 | topleveldefs[tname] = jdef 41 | } else if len(jdef.AllOf) == 2 { 42 | jdef.AllOf[1].base = unRef(jdef.AllOf[0].Ref) 43 | jdef = jdef.AllOf[1] 44 | } else { 45 | panic(tname) 46 | } 47 | if len(jdef.Type) != 1 { 48 | panic(tname) 49 | } 50 | topleveldefs[tname] = jdef 51 | } 52 | jdefs.Defs = topleveldefs 53 | return &jdefs, nil 54 | } 55 | 56 | // Generate a Go package source with type-defs representing the `Defs` in `jsd` (typically obtained via `NewJsonSchema`). 57 | // 58 | // Arguments beyond `goPkgName` generate further code beyond the type-defs: these may all be `nil`/zeroed, or if one "sounds like what you need", check the source for how they're handled otherwise =) 59 | func (me *JsonSchema) Generate(goPkgName string, generateDecodeHelpersForBaseTypeNames map[string]string, generateHandlinScaffoldsForBaseTypes map[string]string, generateCtorsForBaseTypes ...string) string { 60 | var buf ustr.Buf 61 | writedesc := func(ind int, desc string) { 62 | writeDesc(ind, &buf, desc) 63 | } 64 | writedesc(0, me.Title+"\n\n"+me.Desc+"\n\n"+GoPkgDesc) 65 | buf.Writeln("package " + goPkgName) 66 | if generateDecodeHelpersForBaseTypeNames != nil && len(generateDecodeHelpersForBaseTypeNames) > 0 { 67 | buf.Writeln("import \"encoding/json\"") 68 | buf.Writeln("import \"errors\"") 69 | buf.Writeln("import \"strings\"") 70 | } 71 | ctorcandidates := map[string][]string{} 72 | for tname, tdef := range me.Defs { 73 | buf.Writeln("\n\n") 74 | tdef.updateDescBasedOnStrEnumVals() 75 | writedesc(0, tdef.Desc) 76 | if tdef.Type[0] == "object" { 77 | if tdef.Props == nil || len(tdef.Props) == 0 { 78 | if tdef.TMap != nil { 79 | buf.Writelnf("type %s map[string]%s", tname, tdef.TMap.genTypeName(0)) 80 | } else { 81 | buf.Writelnf("type %s %s", tname, TypeMapping["object"]) 82 | } 83 | } else { 84 | buf.Writelnf("type %s struct {", tname) 85 | if len(tdef.base) > 0 { 86 | writedesc(1, me.Defs[tdef.base].Desc) 87 | buf.Writelnf("\t%s", tdef.base) 88 | } 89 | tdef.genStructFields(1, &buf) 90 | if ustr.In(tdef.base, generateCtorsForBaseTypes...) { 91 | for td := tdef; td != nil; td = me.Defs[td.base] { 92 | for pname, pdef := range td.Props { 93 | if len(pdef.Type) == 1 && pdef.Type[0] == "string" && len(pdef.Enum) == 1 && !ustr.In(pname, ctorcandidates[tname]...) { 94 | ctorcandidates[tname] = append(ctorcandidates[tname], pname) 95 | } 96 | } 97 | } 98 | } 99 | buf.Writelnf("\n} // struct %s\n", tname) 100 | if generateDecodeHelpersForBaseTypeNames != nil || generateHandlinScaffoldsForBaseTypes != nil || len(generateCtorsForBaseTypes) > 0 { 101 | buf.Writeln("func (me *" + tname + ") propagateFieldsToBase() {") 102 | if bdef, okb := me.Defs[tdef.base]; okb && bdef != nil { 103 | if bdef.Props != nil { 104 | for pname := range tdef.Props { 105 | if _, okp := bdef.Props[pname]; okp { 106 | buf.Writeln(" me." + tdef.base + "." + bdef.propNameToFieldName(pname) + " = me." + tdef.propNameToFieldName(pname)) 107 | } 108 | } 109 | } 110 | buf.Writeln(" me." + tdef.base + ".propagateFieldsToBase()") 111 | } 112 | buf.Writeln("}") 113 | } 114 | } 115 | } else { 116 | buf.Writelnf("type %s %s", tname, tdef.genTypeName(0)) 117 | } 118 | } 119 | me.generateCtors(&buf, generateCtorsForBaseTypes, ctorcandidates) 120 | if generateDecodeHelpersForBaseTypeNames != nil { 121 | for gdhfbtn, pname := range generateDecodeHelpersForBaseTypeNames { 122 | me.generateDecodeHelper(&buf, gdhfbtn, pname, generateDecodeHelpersForBaseTypeNames) 123 | } 124 | } 125 | if generateHandlinScaffoldsForBaseTypes != nil { 126 | for btnamein, btnameout := range generateHandlinScaffoldsForBaseTypes { 127 | me.generateHandlingScaffold(&buf, btnamein, btnameout, ctorcandidates) 128 | } 129 | } 130 | return buf.String() 131 | } 132 | -------------------------------------------------------------------------------- /jsonschema-extras.go: -------------------------------------------------------------------------------- 1 | package fromjsd 2 | 3 | import ( 4 | "github.com/go-leap/str" 5 | ) 6 | 7 | func (me *JsonSchema) generateCtors(buf *ustr.Buf, baseTypeNames []string, ctorcandidates map[string][]string) { 8 | for _, btname := range baseTypeNames { 9 | buf.Writeln("\nfunc Base" + btname + " (some" + btname + " interface{}) (base" + btname + " *" + btname + ") {") 10 | buf.Writeln(" switch me :" + "= some" + btname + ".(type) {") 11 | for tname, tdef := range me.Defs { 12 | if tdef.base == btname { 13 | buf.Writeln(" case *" + tname + ": base" + btname + " = &me." + btname) 14 | } 15 | } 16 | buf.Writeln(" }") 17 | buf.Writeln(" return") 18 | buf.Writeln("}") 19 | } 20 | for tname, pnames := range ctorcandidates { 21 | if tdef := me.Defs[tname]; tdef != nil { 22 | if len(pnames) > 0 { 23 | buf.Writeln("\n// Returns a new `" + tname + "` with the following fields set: `" + ustr.Join(ustr.Map(pnames, tdef.propNameToFieldName), "`, `") + "`") 24 | buf.Writeln("func New" + tname + "() *" + tname + " {") 25 | buf.Writeln(" new" + tname + " :" + "= " + tname + "{}") 26 | for _, pname := range pnames { 27 | if pdef := tdef.Props[pname]; pdef != nil { 28 | buf.Writeln(" new" + tname + "." + tdef.propNameToFieldName(pname) + " = \"" + pdef.Enum[0] + "\"") 29 | } else { 30 | for bdef := me.Defs[tdef.base]; bdef != nil; bdef = me.Defs[bdef.base] { 31 | if pdef = bdef.Props[pname]; pdef != nil && len(pdef.Type) == 1 && pdef.Type[0] == "string" && len(pdef.Enum) == 1 { 32 | buf.Writeln(" new" + tname + "." + tdef.propNameToFieldName(pname) + " = \"" + pdef.Enum[0] + "\"") 33 | } 34 | } 35 | } 36 | } 37 | buf.Writeln(" new" + tname + ".propagateFieldsToBase()") 38 | buf.Writeln(" return &new" + tname) 39 | buf.Writeln("}") 40 | } 41 | } 42 | } 43 | } 44 | 45 | func (me *JsonSchema) generateDecodeHelper(buf *ustr.Buf, forBaseTypeName string, byPropName string, all map[string]string) { 46 | tdefs := []*JsonDef{} 47 | pmap := map[string]string{} 48 | for tname, tdef := range me.Defs { 49 | if tdef.base == forBaseTypeName { 50 | tdefs = append(tdefs, tdef) 51 | if pdef, ok := tdef.Props[byPropName]; ok && pdef != nil { 52 | if len(pdef.Type) != 1 { 53 | panic(tname + "." + byPropName + " has types: " + ustr.Int(len(pdef.Type))) 54 | } else if pdef.Type[0] != "string" { 55 | panic(tname + "." + byPropName + " is " + pdef.Type[0]) 56 | } else if len(pdef.Enum) != 1 { 57 | panic(tname + "." + byPropName + " has " + ustr.Int(len(pdef.Enum))) 58 | } else if ustr.Has(pdef.Enum[0], "\"") { 59 | panic(tname + "." + byPropName + " has a quote-mark in: " + pdef.Enum[0]) 60 | } else if _, exists := pmap[pdef.Enum[0]]; exists { 61 | panic(tname + "." + byPropName + " would overwrite existing: " + pdef.Enum[0]) 62 | } else { 63 | pmap[pdef.Enum[0]] = tname 64 | } 65 | } 66 | } 67 | } 68 | buf.Writeln("\n\n// TryUnmarshal" + forBaseTypeName + " attempts to unmarshal JSON string `js` (if it starts with a `{` and ends with a `}`) into a `struct` based on `" + forBaseTypeName + "` as follows:") 69 | for pval, tname := range pmap { 70 | buf.WriteString("// \n// If `js` contains `\"" + byPropName + "\":\"" + pval + "\"`, attempts to unmarshal ") 71 | if _, ok := all[tname]; ok { 72 | buf.Writeln("via `TryUnmarshal" + tname + "`") 73 | } else { 74 | buf.Writeln("into a new `" + tname + "`.") 75 | } 76 | } 77 | badjfielderrmsg := forBaseTypeName + ": encountered unknown JSON value for " + byPropName + ": " 78 | buf.Writeln("// \n// Otherwise, `err`'s message will be: `" + badjfielderrmsg + "` followed by the `" + byPropName + "` value encountered.") 79 | buf.Writeln("// \n// In general: the `err` returned may be either `nil`, the above message, or an `encoding/json.Unmarshal()` return value.") 80 | buf.Writeln("// `ptr` will be a pointer to the unmarshaled `struct` value if that succeeded, else `nil`.") 81 | buf.Writeln("// Both `err` and `ptr` will be `nil` if `js` doesn't: start with `{` and end with `}` and contain `\"" + byPropName + "\":\"` followed by a subsequent `\"`.") 82 | buf.Writeln(`func TryUnmarshal` + forBaseTypeName + ` (js string) (ptr interface{}, err error) {`) 83 | buf.Writeln(` if len(js)==0 || js[0]!='{' || js[len(js)-1]!='}' { return }`) 84 | // it's only due to buggy syntax-highlighting that all generated := below are all split out into :` + `= 85 | buf.Writeln(` i1 :` + `= strings.Index(js, "\"` + byPropName + `\":\"") ; if i1<1 { return }`) 86 | buf.Writeln(` subjs :` + `= js[i1+4+` + ustr.Int(len(byPropName)) + `:]`) 87 | buf.Writeln(` i2 :` + `= strings.Index(subjs, "\"") ; if i2<1 { return }`) 88 | pvalvar := byPropName + `_of_` + forBaseTypeName 89 | buf.Writeln(` ` + pvalvar + ` :` + `= subjs[:i2] ; switch ` + pvalvar + ` {`) 90 | for pval, tname := range pmap { 91 | if _, ok := all[tname]; ok { 92 | buf.Writeln(` case "` + pval + `": ptr,err = TryUnmarshal` + tname + `(js)`) 93 | } else { 94 | buf.Writeln(` case "` + pval + `": var val ` + tname + ` ; if err = json.Unmarshal([]byte(js), &val); err==nil { val.propagateFieldsToBase() ; ptr = &val }`) 95 | } 96 | } 97 | buf.Writeln(` default: err = errors.New("` + badjfielderrmsg + `" + ` + pvalvar + `)`) 98 | buf.Writeln(` }`) 99 | buf.Writeln(` return`) 100 | buf.Writeln(`}`) 101 | } 102 | 103 | func (me *JsonSchema) generateHandlingScaffold(buf *ustr.Buf, baseTypeNameIn string, baseTypeNameOut string, ctorcandidates map[string][]string) { 104 | inouts := map[string]string{} 105 | for tnameout, tdefout := range me.Defs { 106 | if tdefout.base == baseTypeNameOut { 107 | tnamein := tnameout[0:len(tnameout)-len(baseTypeNameOut)] + baseTypeNameIn 108 | if _, ok := me.Defs[tnamein]; ok { 109 | inouts[tnamein] = tnameout 110 | } 111 | } 112 | } 113 | for tni, tno := range inouts { 114 | buf.Writeln("\n// Called by `Handle" + baseTypeNameIn + "` (after it unmarshaled the given `" + tni + "`) to further populate the given `" + tno + "` before returning it to its caller (in addition to this handler's returned `error`).") 115 | buf.Writeln("var On" + tni + " func(*" + tni + ", *" + tno + ")error") 116 | } 117 | buf.Writeln("\n// If a type-switch on `in" + baseTypeNameIn + "` succeeds, `out" + baseTypeNameOut + "` points to a `" + baseTypeNameOut + "`-based `struct` value containing the `" + baseTypeNameOut + "` initialized by the specified `initNew" + baseTypeNameOut + "` and further populated by the `OnFoo" + baseTypeNameIn + "` handler corresponding to the concrete type of `in" + baseTypeNameIn + "` (if any). The only `err` returned, if any, is that returned by the specialized `OnFoo" + baseTypeNameIn + "` handler.") 118 | buf.Writeln("func Handle" + baseTypeNameIn + "(in" + baseTypeNameIn + " interface{}, initNew" + baseTypeNameOut + " func(*" + baseTypeNameIn + ", *" + baseTypeNameOut + ")) (out" + baseTypeNameOut + " interface{}, base" + baseTypeNameOut + " *" + baseTypeNameOut + ", handled bool, err error) {") 119 | buf.Writeln(" switch input :" + "= in" + baseTypeNameIn + ".(type) {") 120 | for tni, tno := range inouts { 121 | _, isptr := ctorcandidates[tno] 122 | buf.Writeln(" case *" + tni + ":") 123 | if isptr { 124 | buf.Writeln(" o :" + "= New" + tno + "()") 125 | } else { 126 | buf.Writeln(" o :" + "= &" + tno + "{}") 127 | } 128 | buf.Writeln(" if initNew" + baseTypeNameOut + "!=nil { initNew" + baseTypeNameOut + "(&input." + baseTypeNameIn + ", &o." + baseTypeNameOut + ") ; o.propagateFieldsToBase() }") 129 | buf.Writeln(" if handled = On" + tni + "!=nil; handled { err = On" + tni + "(input, o) ; o.propagateFieldsToBase() }") 130 | buf.Writeln(" out" + baseTypeNameOut + " , base" + baseTypeNameOut + " = o , &o." + baseTypeNameOut + "") 131 | } 132 | buf.Writeln(" }") 133 | buf.Writeln(" return") 134 | buf.Writeln("}") 135 | } 136 | 137 | func (me *JsonSchema) ForceCopyProps(fromBaseTypeName string, toBaseTypeName string, pnames ...string) { 138 | for _, pname := range pnames { 139 | for tname, tdef := range me.Defs { 140 | if pdef := tdef.Props[pname]; pdef != nil && tdef.base == fromBaseTypeName { 141 | tnalt := ustr.TrimSuff(tname, fromBaseTypeName) + toBaseTypeName 142 | if tdalt := me.Defs[tnalt]; tdalt != nil { 143 | pcopy := *pdef 144 | if tdalt.Props != nil { 145 | tdalt.Props[pname] = &pcopy 146 | } else { 147 | tdalt.Props = map[string]*JsonDef{pname: &pcopy} 148 | } 149 | } 150 | } 151 | } 152 | } 153 | } 154 | --------------------------------------------------------------------------------