合并Go中的动态数据结构

时间:2016-06-02 16:17:20

标签: json go struct

我有这个传入的有效载荷,我无法改变。

{
"source": "some random source",
"table": "hosts_table",
"data": [
    ["address", "id", "services_with_info"],
    ["0.0.0.1", 1111, [
            ["service_3", "is very cool", 1],
            ["service_4", "is very cool", 2]
        ]
    ],
    ["0.0.0.2", 2222, [
            ["service_3", "is very cool", 3],
            ["service_4", "is very cool", 4]
        ]
    ]
]}

我需要获取"数据的第一个索引"并创建一个新的JSON对象,看起来像这样......

"data": [{
"address": "0.0.0.1", 
"id": 1111, 
"services_with_info":[
    {
        "service_name": "service_1", 
        "service_message": "is very cool", 
        "service_id": 1
    }, 
    {...}
]}, 
{...}]

然后从中构建[]Host数据结构为5k" hosts"长。我能够将它映射到一个结构,但需要先将它转换为这种格式。我理解如何解组JSON,但前提是我可以将有效负载转换为上述内容。

2 个答案:

答案 0 :(得分:3)

您可以使用json.Unmarshal并根据您的条件解析数据。我只为"data"执行此操作,您可以为"services_with_info"执行此操作

b := []byte(`{
            "source": "some random source",
            "table": "hosts_table",
            "data": [
                ["address", "id", "services_with_info"],
                ["0.0.0.1", 1111, [
                        ["service_3", "is very cool", 1],
                        ["service_4", "is very cool", 2]
                    ]
                ],
                ["0.0.0.2", 2222, [
                        ["service_3", "is very cool", 3],
                        ["service_4", "is very cool", 4]
                    ]
                ]
            ]}`)
var f interface{}
err := json.Unmarshal(b, &f)
if err != nil {
    fmt.Println(err)
    return
}
m := f.(map[string]interface{})
result := make(map[string]interface{})
results := make(map[string][]map[string]interface{})
for k, v := range m {
    if k == "data" {
        s := v.([]interface{})
        header := make([]interface{}, 3)
        for i, u := range s {
            if i == 0 {
                header = u.([]interface{})
            } else {
                row := u.([]interface{})
                for j, k := range header {
                    result[k.(string)] = row[j]
                }
                results["data"] = append(results["data"], result)
            }
        }
    }
}
fmt.Println(results)

此处"results"根据需要为"data"

答案 1 :(得分:2)

我不确定我是否理解你想要的东西。

可能是这样的事情? 可能它需要一些工作,比如制作指向结构的指针切片而不是结构切片以防止分配和复制,错误处理,转换值的更多自定义逻辑,匿名/封装在转换过程中使用的私有结构,添加json标记到那些结构等。

我在IncomingPaylod上为数据字段创建自定义Unmarshaller:解析预期数据,将其转换为[] MyData并用它更新数据字段。

我为expected_data和expected_services_with_info创建了自定义Unmarshallers,因为我们期望它作为值数组(3个值:string,int和[array of string,int(?),int]),但我想将它转换为nice structs 。如果你不喜欢它,你可以删除它,将预期的数据解组到[] interface {}并像[] interface {} {string,int,[] interface {} {string,int,int}}一样使用它。很容易弄错,所以我更喜欢结构,更容易阅读和维护和重构(我认为你的应用程序中有更多的字段)。

https://play.golang.org/p/xHTvyhecra

package main

import (
    "encoding/json"
    "fmt"
    "strconv"
)

type IncomingPayload struct {
    Source string      `json:"source"`
    Table  string      `json:"table"`
    Data   MyDataSlice `json:"data"`
}
type MyDataSlice []MyData

type MyData struct {
    Address            string              `json:"address"`
    ID                 string              `json:"id"`
    Services_with_info []MyServiceWithInfo `json:"services_with_info"`
}

type MyServiceWithInfo struct {
    ServiceName    string `json:"service_name"`
    ServiceMessage string `json:"service_message"`
    ServiceID      int    `json:"service_id"`
}

type expected_data struct {
    IP   string
    ID   int
    Info []expected_services_with_info
}

type expected_services_with_info struct {
    Name string
    Desc string
    ID   int
}

func (ed *expected_data) UnmarshalJSON(buf []byte) error {
    tmp := []interface{}{&ed.IP, &ed.ID, &ed.Info}
    // converts ["address", "id", "services_with_info"] into a struct
    // will unmarshall "services_with_info" (ed.Info) with *expected_services_with_info.UnmarshalJSON
    json.Unmarshal(buf, &tmp)
    return nil
}

func (es *expected_services_with_info) UnmarshalJSON(buf []byte) error {
    tmp := []interface{}{&es.Name, &es.Desc, &es.ID}
    // converts ["service_3", "is very cool", 1] into a struct
    json.Unmarshal(buf, &tmp)
    return nil
}

func (md *MyDataSlice) UnmarshalJSON(p []byte) error {
    var incoming_data_slice []expected_data
    json.Unmarshal(p, &incoming_data_slice)
    //fmt.Println("incoming", incoming_data_slice)

    //transform incoming_data_slice to your needs using your data type
    for i := range incoming_data_slice {
        my_data := MyData{
            Address: incoming_data_slice[i].IP,               //copy
            ID:      strconv.Itoa(incoming_data_slice[i].ID), //some transformation
            //nil slice is totally fine, but if you wish you can do
            //Data: make(MyDataSlice, len(incoming_data_slice)),

        }

        //not sure what would be best: "i := range data" or "_, v := range data" (second one makes a copy? and causes allocation)
        for j := range incoming_data_slice[i].Info {
            tmp := MyServiceWithInfo{
                ServiceName: incoming_data_slice[i].Info[j].Name,
                ServiceMessage: incoming_data_slice[i].Info[j].Desc,
                ServiceID: incoming_data_slice[i].Info[j].ID,
            }
            my_data.Services_with_info = append(my_data.Services_with_info, tmp)
        }

        //and populate
        *md = append(*md, my_data)
    }

    return nil
}

func main() {

    test_json := `{
"source": "some random source",
"table": "hosts_table",
"data": [
    ["address", "id", "services_with_info"],
    ["0.0.0.1", 1111, [
            ["service_3", "is very cool", 1],
            ["service_4", "is very cool", 2]
        ]
    ],
    ["0.0.0.2", 2222, [
            ["service_3", "is very cool", 3],
            ["service_4", "is very cool", 4]
        ]
    ]
]}`

    var payload IncomingPayload
    json.Unmarshal([]byte(test_json), &payload)
    fmt.Println("payload", payload)

    buf, _ := json.MarshalIndent(payload, "", "\t")
    fmt.Println(string(buf))

}