首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >接口的自定义UnmarshalYAML接口及其实现

接口的自定义UnmarshalYAML接口及其实现
EN

Stack Overflow用户
提问于 2020-03-11 13:02:02
回答 1查看 1.6K关注 0票数 0

我实现了一个接口Fruit,以及它的两个实现:AppleBanana

在这两个实现的对象中,我希望从yaml文件中加载数据:

代码语言:javascript
复制
capacity: 4
Apple:
- name: "apple1"
  number: 1
- name: "apple2"
  number: 1
Banana:
- name: "banana1"
  number: 2

我实现了UnmarshalYaml接口来将数据加载到对象中:

代码语言:javascript
复制
package main

import (
    "errors"
    "gopkg.in/yaml.v3"
    "log"
    "fmt"
)

type FruitBasket struct {
    Capacity int `yaml:"capacity"`
    Fruits []Fruit
}

func NewFruitBasket() *FruitBasket {
    fb := new(FruitBasket)

    return fb
}

type Fruit interface {
    GetFruitName() string
    GetNumber() int
}

type Apple struct {
    Name string `yaml:"name"`
    Number int `yaml:"number"`
}

type Banana struct {
    Name string `yaml:"name"`
    Number int `yaml:"number"`
}

func (apple *Apple) GetFruitName() string {
    return apple.Name
}

func (apple *Apple) GetNumber() int {
    return apple.Number
}

func (banana *Banana) GetFruitName() string {
    return banana.Name
}

func (banana *Banana) GetNumber() int {
    return banana.Number
}

type tmpFruitBasket struct {
    Capacity int `yaml:"capacity"`
    Fruits []map[string]yaml.Node
}

func (fruitBasket *FruitBasket) UnmarshalYAML(value *yaml.Node) error {
    var tmpFruitBasket tmpFruitBasket

    if err := value.Decode(&tmpFruitBasket); err != nil {
        return err
    }

    fruitBasket.Capacity = tmpFruitBasket.Capacity

    fruits := make([]Fruit, 0, len(tmpFruitBasket.Fruits))

    for i := 0; i < len(tmpFruitBasket.Fruits); i++ {
        for tag, node := range tmpFruitBasket.Fruits[i] {
            switch tag {
            case "Apple":
                apple := &Apple{}
                if err := node.Decode(apple); err != nil {
                    return err
                }

                fruits = append(fruits, apple)
            case "Banana":
                banana := &Banana{}
                if err := node.Decode(banana); err != nil {
                    return err
                }

                fruits = append(fruits, banana)
            default:
                return errors.New("Failed to interpret the fruit of type: \"" + tag + "\"")
            }
        }
    }

    fruitBasket.Fruits = fruits

    return nil
}

func main() {
    data := []byte(`
capacity: 4
Apple:
- name: "apple1"
  number: 1
- name: "apple2"
  number: 1
Banana:
- name: "banana1"
  number: 2
`)

    fruitBasket := NewFruitBasket()

    err := yaml.Unmarshal(data, &fruitBasket)

    if err != nil {
        log.Fatalf("error: %v", err)
    }

    fmt.Println(fruitBasket.Capacity)

    for i := 0; i < len(fruitBasket.Fruits); i++ {
        switch fruit := fruitBasket.Fruits[i].(type) {
        case *Apple:
            fmt.Println(fruit.Name)
            fmt.Println(fruit.Number)
        }
    }
}

然而,这是行不通的。似乎没有加载AppleBanana标记的数据。可能是因为我的Fruits结构中的tmpFruitBasket切片缺少yaml标志。但是,由于Fruit是一个接口,所以我不能定义yaml标志。在未来,我希望实现其他表示具体成果的结构(例如草莓),实现接口Fruit

知道怎么解决这个问题吗?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-03-11 13:59:11

这是您需要的中间类型:

代码语言:javascript
复制
type tmpFruitBasket struct {
  Capacity int
  Apple    []yaml.Node `yaml:"Apple"`
  Banana   []yaml.Node `yaml:"Banana"`
}

然后,加载函数将如下所示:

代码语言:javascript
复制
// helper to load a list of nodes as a concrete type
func appendFruits(fruits []Fruit, kind reflect.Type, input []yaml.Node) ([]Fruit, error) {
  for i := range input {
    val := reflect.New(kind).Interface()
    if err := input[i].Decode(val); err != nil {
      return nil, err
    }
    fruits = append(fruits, val.(Fruit))
  }
  return fruits, nil
}


func (fruitBasket *FruitBasket) UnmarshalYAML(value *yaml.Node) error {
    var tmp tmpFruitBasket

    if err := value.Decode(&tmp); err != nil {
        return err
    }

    fruitBasket.Capacity = tmp.Capacity

    var fruits []Fruit
    var err error
    // sadly, there is no nicer way to get the reflect.Type of Apple / Banana
    fruits, err = appendFruits(
      fruits, reflect.TypeOf((*Apple)(nil)).Elem(), tmp.Apple)
    if err != nil {
      return err
    }
    fruits, err = appendFruits(
      fruits, reflect.TypeOf((*Banana)(nil)).Elem(), tmp.Banana)
    if err != nil {
      return err
    }

    fruitBasket.Fruits = fruits
    return nil
}

编辑:--如果您坚持将每个类型排序成一个专用的片段,当然可以直接将它们键入为[]Apple[]Banana,然后将它们合并。这个答案是对动态加载输入到不同类型的问题的继续,从前面的问题开始。这样做只有在您在某个时候不知道静态类型时才有意义(例如,如果您提供了一个API来在运行时添加其他的水果类型)。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60636689

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档