Reputation: 777
I would like to deserialize some YAML
data in Golang
based on an OpenApi 3
schema. The specification contains a polymorphic array
. How can it be deserialized to the corresponding struct types in Golang based on a discriminator
type? I tried to use an interface as a base type in the Traits array, but then I'm not able to upcast
the individual items with an explicit cast. Is this an issue of the go-yaml library I'm using or in the structure of my structs / interface types?
Here is my crude attempt:
import (
"fmt"
"github.com/goccy/go-yaml"
//"gopkg.in/yaml.v3"
)
func main() {
discriminators()
}
func discriminators() {
var yamlData = `
spec:
traits:
- name: Web dashboard
type: ui
summary: hmmm ET
- name: Wiki
type: documentation
description: Description
`
data := Data{}
if err := yaml.Unmarshal([]byte(yamlData), &data); err == nil {
trait := data.Spec.Traits[0]
//type=ui
uiTrait := trait.(UiTrait)
println(uiTrait.Description)
}
}
type TraitInterface interface {
getName() string //`yaml:"name"`
}
type Trait struct {
Name string `yaml:"name"`
Type_ string `yaml:"type"`
}
type UiTrait struct {
//Trait Trait `json:"trait"`
Name string `yaml:"name"`
Type_ string `yaml:"type"`
Description string `json:"description,omitempty"`
}
type DocumentationTrait struct {
//Trait Trait `json:"trait"`
Name string `yaml:"name"`
Type_ string `yaml:"type"`
Summary string `json:"summary,omitempty"`
}
type Specification struct {
Traits []interface{} `yaml:"traits,omitempty"`
//Traits []TraitInterface `yaml:"traits,omitempty"`
}
type Data struct {
Spec Specification `yaml:"spec"`
}
func (a UiTrait) getName() string {
return a.Name
}
OpenApi 3 specification with oneOf
:
openapi: "3.0.0"
components:
schemas:
Specification:
type: object
additionalProperties: false
properties:
traits:
type: array
items:
oneOf:
- $ref: "#/components/schemas/UiTrait"
- $ref: "#/components/schemas/DocumentationTrait"
discriminator:
propertyName: type
mapping:
ui: "#/components/schemas/UiTrait"
documentation: "#/components/schemas/DocumentationTrait"
Trait:
type: object
additionalProperties: false
properties:
name:
type: string
type:
description: Type of trait
type: string
description:
description: Description
type: string
required:
- name
- type
DocumentationTrait:
allOf:
- $ref: "#/components/schemas/Trait"
- type: object
additionalProperties: false
properties:
summary:
type: string
UiTrait:
allOf:
- $ref: "#/components/schemas/Trait"
- type: object
additionalProperties: false
properties:
description:
type: string
Thank you!
Upvotes: 1
Views: 1492
Reputation: 20467
The type of the items is map[string] interface{}, that's why the type assertion fails. It's not a conversion. It just checks (asserts) if the interface is actually that type. See https://go.dev/tour/methods/15.
I am suggesting to use mapstructure which has been created with this usecase in mind.
Perhaps we can't populate a specific structure without first reading the "type" field from the JSON. We could always do two passes over the decoding of the JSON (reading the "type" first, and the rest later). However, it is much simpler to just decode this into a map[string]interface{} structure, read the "type" key, then use something like this library to decode it into the proper structure.
Also note that you have a bug in your types. The UI type has a summary and the documentation type as a description, but in your code it's switched around.
func discriminators() {
var yamlData = `
spec:
traits:
- name: Web dashboard
type: ui
summary: hmmm ET
- name: Wiki
type: documentation
description: Description
`
data := Data{}
err := yaml.Unmarshal([]byte(yamlData), &data)
if err != nil {
panic(err)
}
trait := data.Spec.Traits[0]
var ut UiTrait
if err := mapstructure.Decode(trait, &ut); err != nil {
panic(err)
}
println(ut.Summary)
}
type Data struct {
Spec Specification `yaml:"spec"`
}
type Trait struct {
Name string `yaml:"name"`
Type_ string `yaml:"type"`
}
type UiTrait struct {
Name string `yaml:"name"`
Type_ string `yaml:"type"`
Summary string `json:"summary,omitempty"`
}
type DocumentationTrait struct {
Name string `yaml:"name"`
Type_ string `yaml:"type"`
Description string `json:"description,omitempty"`
}
type Specification struct {
Traits []interface{} `yaml:"traits,omitempty"`
}
https://go.dev/play/p/ZjrEzn6jEYQ
Upvotes: 1