解组将类型更改为结构的流

时间:2017-12-12 18:35:31

标签: json go struct unmarshalling

我目前正在处理进入我的应用程序的json对象流,并且在确定解析它们的最佳方法时遇到了一些困难。流由具有已定义类型的对象组成。问题是对象中的一个字段是更改类型。它看起来像这样:

[{
  "status": "closed",
  "type": "transaction",
  "transaction": {
    "TransactionType": "TypeA",
    "Account": "Some string",
    "Fee": "14",
    "date": 45325680
  },
  "validated": true
},

{
  "status": "closed",
  "type": "transaction",
  "transaction": {
    "TransactionType" : "TypeB",
    "Account" : "Some string",
    "Fee": "42",
    "Destination" : "Some string"
  },
  "validated": true
}]

你可以看到"父母"不会改变,但"交易"确实。我从"交易"中删除了很多字段。字段使其更容易解释,但这是一个具有10-ish公共字段和10-ish更改字段的类型,这些字段取决于类型。还有10种事务类型,这使得将所有内容放入一个结构并拥有大量可选字段非常烦人。我也在考虑每个事务类型的一个结构,但这不起作用,因为那时无法指定该字段在父类中应该具有的类型。

我如何有效地解析这些对象?不幸的是,我无法改变流出来的元素的结构。解决这个问题的最佳方法是什么?

4 个答案:

答案 0 :(得分:0)

或许将transaction作为map[string]interface{}

示例https://play.golang.com/p/j_u_ztw04M

package main

import (
    "encoding/json"
    "fmt"
)

type Stream []struct {
    Status string `json:"status"`
    Type   string `json:"type"`

    // map instead of struct
    Transaction map[string]interface{} `json:"transaction"`

    Validated bool `json:"validated"`
}

func main() {

    stream := Stream{}

    err := json.Unmarshal(rawj, &stream)
    if err != nil {
        fmt.Println("error:", err)
        return
    }

    for i, s := range stream {
        fmt.Println("thing:", i)
        for k, v := range s.Transaction {

            // do manual stuff here, perhaps long switch on k and/or
            // as suggested by Cerise Limón type switch on v
            fmt.Printf("key: %s, val: %v, val type: %T\n", k, v, v)

        }
        fmt.Println()
    }

}

var rawj = []byte(`[{
  "status": "closed",
  "type": "transaction",
  "transaction": {
    "TransactionType": "TypeA",
    "Account": "Some string",
    "Fee": "14",
    "date": 45325680
  },
  "validated": true
},

{
  "status": "closed",
  "type": "transaction",
  "transaction": {
    "TransactionType" : "TypeB",
    "Account" : "Some string",
    "Fee": "42",
    "Destination" : "Some string"
  },
  "validated": true
}]`)

玩得开心!

答案 1 :(得分:0)

对于"交易" struct我会使公共密钥直接类型(例如stringint等)和可选的密钥指针到它们的类型(*string,{{ 1}}等等,用" omitempty"标记,如果它们不存在于事件中,则设置为nil:

*int

这样公共字段总是在那里,但是如果存在则可能填充默认(空)值和可选字段,如果省略则填充为nil:

type Transaction struct {
  // Common fields are direct types...
  TransactionType string
  Account         string
  Fee             string

  // Optional fields are pointers with "omitempty"...
  Date        *int    `json:",omitempty"`
  Destination *string `json:",omitempty"`
}

答案 2 :(得分:0)

为每种交易类型定义结构,并分两步执行取消编组。首先使用

type Stream []struct {
    ...
    TransactionRaw json.RawMessage `json:"transaction"`
    ...
}

然后使用Regexp.FindStringSubmatch上的TransactionRaw来确定第二次调用json.Unmarshal的类型

type Transaction struct {
    TransactionType string `json:"TransactionType"`
    Account         string `json:"Account"`
    Fee             string `json:"Fee"`
}

type TypeA struct {
    Transaction
    Date int    `json:"date"`
}

等...

Playground example here

答案 3 :(得分:0)

我认为使用反射是[]map[string]interface{}

的一种方式

this answer中,我举了一个关于使用reflect来解码改变类型的例子。