【问题标题】:Dynamically Unmarshaling yaml in golang在golang中动态解组yaml
【发布时间】:2018-05-29 08:25:27
【问题描述】:

编辑:添加问题上下文

我正在为 ArangoDB 数据库制作迁移工具。不知道那是什么?结帐https://www.arangodb.com。现在像大多数迁移工具一样,我的需要创建集合,在逻辑上相当于 SQL 表。它需要创建或删除索引、集合、图表和其他 DB 实体,以及执行任意 AQL(ArangoDB 的类似 SQL 的代数)。

为了支持这一点,用户创建了一系列迁移文件,每个文件都说明了如何处理该步骤。所以步骤 1 可能会说“创建集合用户”,步骤 67 可能会说“删除集合用户——因为我们重命名了它”。我想使用 YAML,因为 ArangoDB 团队明确问我为什么不使用 YAML。由于二进制发行版不需要 Java,Golang 似乎比我现有的 Java 实现更好。

话虽如此,我有几种不同类型的结构存储在不同的 yaml 文件中。我可以通过读取 yaml 的第一行来判断哪个结构在哪个文件中。我希望在阅读文件时尽可能保持干燥。

我试图有一个工厂方法,可以从这段代码中的文件创建正确的结构。我们可以假设所有的 pickT 实例都会返回一个有效的 Migration 接口实现。

func pickT(contents []byte) (Migration, error) {
    s := string(contents)
    switch {
    case collection.MatchString(s):
        return new(Collection), nil
    default:
        return nil, errors.New("Can't determine YAML type")
    }
}

func toStruct(childPath string) Migration {
    contents := open(childPath)

    t, err := pickT(contents)
    if err != nil {
        log.Fatal(err)
    }
    //c := Collection{}
    err = yaml.UnmarshalStrict(contents, &t)
    if err != nil {
        log.Fatal(err)
    }
    return t
}

此代码不起作用。 Yaml 踢出下面的错误。

--- FAIL: TestLoadFromPath (0.00s)
panic: reflect.Set: value of type map[interface {}]interface {} is not assignable to type main.Migration [recovered]
    panic: reflect.Set: value of type map[interface {}]interface {} is not assignable to type main.Migration [recovered]
    panic: reflect.Set: value of type map[interface {}]interface {} is not assignable to type main.Migration

goroutine 5 [running]:
testing.tRunner.func1(0xc4201060f0)
    /usr/local/go/src/testing/testing.go:711 +0x2d2
panic(0x68ade0, 0xc420010cf0)
    /usr/local/go/src/runtime/panic.go:491 +0x283
gopkg.in/yaml%2ev2.handleErr(0xc420057b18)
    /home/jdavenpo/go/src/gopkg.in/yaml.v2/yaml.go:164 +0x9f
panic(0x68ade0, 0xc420010cf0)
    /usr/local/go/src/runtime/panic.go:491 +0x283
reflect.Value.assignTo(0x69e740, 0xc42007d230, 0x15, 0x6f30e4, 0xb, 0x6a45a0, 0xc420010cd0, 0x69e740, 0xc42007d230, 0x15)
    /usr/local/go/src/reflect/value.go:2192 +0x3a6
reflect.Value.Set(0x6a45a0, 0xc420010cd0, 0x194, 0x69e740, 0xc42007d230, 0x15)
    /usr/local/go/src/reflect/value.go:1357 +0xa4
gopkg.in/yaml%2ev2.(*decoder).mapping(0xc420060900, 0xc4200644e0, 0x6a45a0, 0xc420010cd0, 0x194, 0x6a45a0)
    /home/jdavenpo/go/src/gopkg.in/yaml.v2/decode.go:522 +0x8e2
gopkg.in/yaml%2ev2.(*decoder).unmarshal(0xc420060900, 0xc4200644e0, 0x6a45a0, 0xc420010cd0, 0x194, 0xc4200644e0)
    /home/jdavenpo/go/src/gopkg.in/yaml.v2/decode.go:292 +0x136
gopkg.in/yaml%2ev2.(*decoder).document(0xc420060900, 0xc420064480, 0x6a45a0, 0xc420010cd0, 0x194, 0xc4200608c0)
    /home/jdavenpo/go/src/gopkg.in/yaml.v2/decode.go:304 +0x80
gopkg.in/yaml%2ev2.(*decoder).unmarshal(0xc420060900, 0xc420064480, 0x6a45a0, 0xc420010cd0, 0x194, 0x194)
    /home/jdavenpo/go/src/gopkg.in/yaml.v2/decode.go:280 +0x1d2
gopkg.in/yaml%2ev2.unmarshal(0xc42010c000, 0x3b, 0x23b, 0x67c4a0, 0xc420010cd0, 0x1, 0x0, 0x0)
    /home/jdavenpo/go/src/gopkg.in/yaml.v2/yaml.go:101 +0x289
gopkg.in/yaml%2ev2.UnmarshalStrict(0xc42010c000, 0x3b, 0x23b, 0x67c4a0, 0xc420010cd0, 0x0, 0x0)
    /home/jdavenpo/go/src/gopkg.in/yaml.v2/yaml.go:87 +0x58
github.com/deusdat/arangomigo.toStruct(0xc420014360, 0x26, 0x0, 0x0)
    /home/jdavenpo/go/src/github.com/deusdat/arangomigo/migrations.go:102 +0x295
github.com/deusdat/arangomigo.loadFrom(0x6f8059, 0x1a, 0x0, 0x0, 0x0)
    /home/jdavenpo/go/src/github.com/deusdat/arangomigo/migrations.go:67 +0x4d6
github.com/deusdat/arangomigo.TestLoadFromPath(0xc4201060f0)
    /home/jdavenpo/go/src/github.com/deusdat/arangomigo/migrations_test.go:11 +0x48
testing.tRunner(0xc4201060f0, 0x705950)
    /usr/local/go/src/testing/testing.go:746 +0xd0
created by testing.(*T).Run
    /usr/local/go/src/testing/testing.go:789 +0x2de

据我所知,Go 类型会擦除界面,至少在功能上是这样。有什么方法可以动态选择类型,然后让 Yaml 将其转换出来?我知道我可以通过在不同的功能中一遍又一遍地重复相同的逻辑来手动执行此操作,例如下面的功能,但这似乎......很糟糕。

func fromCollectionYaml(content string) Migration {
    c := Collection{}
    err := yaml.Unmarshal(content, &c)
    if err != nil {
        log.Fatal(err)
    }
    return c
}

编辑:添加 Collection 结构和 Yaml

type: collection
Action: create
Name: Users
Journalsize: 10

type Collection struct {
    Name           string
    Action         Action
    ShardKeys      []string
    JournalSize    int
    NumberOfShards int
    WaitForSync    bool
    AllowUserKeys  bool
    Volatile       bool
    Compactable    bool
}

func (this Collection) migrate(action Action, db *arangolite.Database) error {
    return nil
}

在不久的将来,我需要为索引添加结构。图表和其他 ArangoDB 相关功能。每个 Yaml 文件都需要进入一个生成和迁移的结构,它只是一个接口的实例,具有当前与结构一起显示的一个函数。

【问题讨论】:

  • 能否附上集合结构和yaml文件示例?

标签: go yaml


【解决方案1】:

我在 reddit 上找到了这个问题的答案,为了帮助未来的读者,我在下面提供了内容以及我找到它的帖子的链接。

我相信,解决方案是简单地将 t 传递给 Unmarshal,而不是 &t

基本原理:您需要传递一个指向结构的指针,该结构可以采用 将预期数据作为接口中的动态值{} 到 Unmarshal。从 pickT,你返回new(Collection),就是这样一个指针。但是之后, 您首先将其存储在另一种接口类型(迁移)中,然后取 那的指针。意思是,yaml 将 a) 看到它通过了 a 指针和 b) 尝试将值存储在指针对象中。但是指点 是接口类型(然后该接口值将包含 实际结构,但 yaml 不执行此额外重定向)。

如果您改为传递 t (具有接口类型),它将首先是 转换为 interface{} 然后传递 - 所以参数将 直接包含 *Collection,如预期的那样。

这里的区别在于,如果您转换/分配一种接口类型 到另一个,它不会被重新包装,而是一个中的动态值 价值只会转移到另一个。但是如果你存储一个 非接口类型(如指向接口的指针),它将得到 重新包装。

恐怕很难解释。你可以在这里看到效果 太:https://play.golang.org/p/ia181c_Pwp(注意,那个 Printf 也 接受一个接口{})

https://www.reddit.com/r/golang/comments/7k0kpv/please_help_with_dynamically_unmarshaling_yaml_in/

【讨论】:

    猜你喜欢
    • 2015-11-15
    • 1970-01-01
    • 2022-01-21
    • 2021-11-22
    • 2019-01-23
    • 2018-01-14
    • 1970-01-01
    • 2021-02-11
    • 2018-12-21
    相关资源
    最近更新 更多