接口及其实现的自定义 UnmarshalYAML 接口
Custom UnmarshalYAML interface for an interface and its implementations
我实现了一个接口 Fruit
和它的两个实现:Apple
和 Banana
。
我想从 yaml 文件加载数据到两个实现的对象中:
capacity: 4
Apple:
- name: "apple1"
number: 1
- name: "apple2"
number: 1
Banana:
- name: "banana1"
number: 2
我实现了 UnmarshalYaml
接口来将数据加载到我的对象中:
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)
}
}
}
但是,这不起作用。 Apple
和 Banana
标签的数据似乎没有加载。可能是因为我的 tmpFruitBasket
结构中缺少 Fruits
切片的 yaml 标志。但是,由于 Fruit
是一个接口,我无法定义 yaml 标志。将来,我想实现其他表示实现接口 Fruit
.
的具体水果(例如草莓)的结构
知道如何解决这个问题吗?
这是您需要的中间类型:
type tmpFruitBasket struct {
Capacity int
Apple []yaml.Node `yaml:"Apple"`
Banana []yaml.Node `yaml:"Banana"`
}
然后,加载函数将如下所示:
// 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 以在运行时添加其他水果类型)。
我实现了一个接口 Fruit
和它的两个实现:Apple
和 Banana
。
我想从 yaml 文件加载数据到两个实现的对象中:
capacity: 4
Apple:
- name: "apple1"
number: 1
- name: "apple2"
number: 1
Banana:
- name: "banana1"
number: 2
我实现了 UnmarshalYaml
接口来将数据加载到我的对象中:
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)
}
}
}
但是,这不起作用。 Apple
和 Banana
标签的数据似乎没有加载。可能是因为我的 tmpFruitBasket
结构中缺少 Fruits
切片的 yaml 标志。但是,由于 Fruit
是一个接口,我无法定义 yaml 标志。将来,我想实现其他表示实现接口 Fruit
.
知道如何解决这个问题吗?
这是您需要的中间类型:
type tmpFruitBasket struct {
Capacity int
Apple []yaml.Node `yaml:"Apple"`
Banana []yaml.Node `yaml:"Banana"`
}
然后,加载函数将如下所示:
// 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 以在运行时添加其他水果类型)。