如何在Golang中使用鉴别器反序列化基于多态数组的OpenAPI 3 YAML文件?

huangapple go评论89阅读模式
英文:

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"`
}

https://go.dev/play/p/ZjrEzn6jEYQ

huangapple
  • 本文由 发表于 2022年5月20日 16:22:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/72315729.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定