英文:
How to deserialize an OpenAPI 3 YAML file with a polymorphic array based on a discriminator in Golang?
问题
我将为您翻译以下内容:
我想在Golang中根据OpenApi 3模式反序列化一些YAML数据。规范中包含一个多态数组。如何根据鉴别器类型在Golang中将其反序列化为相应的结构类型?我尝试在Traits数组中使用接口作为基本类型,但是我无法使用显式转换将各个项向上转换。这是我使用的go-yaml库的问题还是我的结构/接口类型的问题?
这是我粗略的尝试:
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
}
带有oneOf
的OpenApi 3规范:
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
谢谢!
英文:
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!
答案1
得分: 1
items的类型是map[string]interface{},这就是为什么类型断言失败的原因。这不是一种转换,它只是检查(断言)接口是否实际上是该类型。请参阅https://go.dev/tour/methods/15。
我建议使用mapstructure,它是专为这种用例而创建的。
也许我们不能在没有先读取JSON中的"type"字段的情况下填充特定的结构。我们可以始终对JSON的解码进行两次遍历(先读取"type",然后再读取其余部分)。然而,将其解码为map[string]interface{}结构,读取"type"键,然后使用类似这个库的方法将其解码为正确的结构会更简单。
还要注意,您的类型中存在错误。UI类型具有摘要,而文档类型具有描述,但在您的代码中它们被颠倒了。
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
英文:
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"`
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论