dream6120 2015-02-25 05:33
浏览 69

使用不同的密钥进行JSON解组

I'm trying to unmarshal some JSON from different sources that may have different keys. For instance, I may have:

{
    "a": 1,
    "b": 2
}

Or I may have:

{
    "c": 1,
    "b": 2
}

In this case, I can guarantee that "b" will be there. However, I want "a" and "c" to be represented the same way. In effect, what I want is:

type MyJson struct {
    Init int `json:"a",json:"c"`
    Sec  int `json:"b"
}

Basically I want the unmarshaller to look for either key and set it as Init. Now this doesn't actually work (or I wouldn't be posting). Unmarshalling the first gives me what I want, while the second sets Init to 0. My ideal option would be to unmarshal to a struct, with one of two possibilities:

  1. I represent the struct with multiple tags as above
  2. I define multiple structs, but am able to choose which to unmarshal to

I tried to implement number 2 by making two different structs and a map, but it seems I can't make a map with a type as the second value:

type MyJson1 struct {
    Init int `json:"a"`
    Sec  int `json:"b"`
}

type MyJson2 struct {
    Init int `json:"c"`
    Sec  int `json:"b"`
}

Is there a way to define a set of structs that behaves like an interface? That is, they all have the same fields, but are defined differently. Or maybe there's another way. I could make the fields unmarshalling "a" and "c" other fields, then set Init accordingly. But this doesn't scale beyond a couple of variants.

Thanks!

  • 写回答

2条回答 默认 最新

  • dongshanji3102 2015-02-25 07:55
    关注

    One possibility is to define a struct which has a field for all the variants for your possible inputs, and for convenience provide a method for this struct which will return the field that was found in the input:

    type MyJson struct {
        A *int `json:"a"`
        C *int `json:"c"`
    
        Sec int `json:"b"`
    }
    
    func (j *MyJson) Init() int {
        if j.A == nil {
            return *j.C
        }
        return *j.A
    }
    

    Using it:

    inputs := []string{
        `{"a": 1, "b": 2}`,
        `{"c": 1, "b": 2}`}
    
    for _, input := range inputs {
        var my MyJson
        if err := json.Unmarshal([]byte(input), &my); err != nil {
            panic(err)
        }
        fmt.Printf("Init: %v, Sec: %v
    ", my.Init(), my.Sec)
    }
    

    Output, as expected (try it on the Go Playground):

    Init: 1, Sec: 2
    Init: 1, Sec: 2
    

    And a Little Trick:

    In the original struct we added 2 fields for the 2 possible variants. I defined them as pointers so we can detect which one was found in the JSON input. Now if before unmarshalling we set these pointers to point to the same value, that's all we need: doesn't matter which variant of the input JSON we use, the same value will be set in memory, so you can always just read/refer the Init struct field:

    type MyJson struct {
        Init *int `json:"a"`
        Init2 *int `json:"c"`
    
        Sec int `json:"b"`
    }
    
    func main() {
        inputs := []string{
            `{"a": 1, "b": 2}`,
            `{"c": 1, "b": 2}`}
    
        for _, input := range inputs {
            var my MyJson
            my.Init = new(int) // Allocate an int
            my.Init2 = my.Init // Set Init2 to point to the same value
    
            if err := json.Unmarshal([]byte(input), &my); err != nil {
                panic(err)
            }
            fmt.Printf("Init: %v, Sec: %v
    ", *my.Init, my.Sec)
        }
    }
    

    Try it on the Go Playground.

    You can create a function which creates and sets up your MyJson ready for unmarshalling like this:

    func NewMyJson() (my MyJson) {
        my.Init = new(int) // Allocate an int
        my.Init2 = my.Init // Set Init2 to point to the same value
        return
    }
    

    And so using it becomes this simple:

    var my = NewMyJson()
    err := json.Unmarshal([]byte(input), &my)
    

    Another Trick on the Tricked variant:

    You can specify the Init field not to be a pointer because it is enough for Init2 to be a pointer and point to Init, so this becomes even more simple and desirable (but then NewMyJson must return a pointer):

    type MyJson struct {
        Init  int `json:"a"`
        Init2 *int `json:"c"`
    
        Sec int `json:"b"`
    }
    
    func NewMyJson() *MyJson {
        my := new(MyJson)
        my.Init2 = &my.Init // Set Init2 to point to Init
        return my
    }
    
    评论

报告相同问题?

悬赏问题

  • ¥15 关于#hadoop#的问题
  • ¥15 (标签-Python|关键词-socket)
  • ¥15 keil里为什么main.c定义的函数在it.c调用不了
  • ¥50 切换TabTip键盘的输入法
  • ¥15 可否在不同线程中调用封装数据库操作的类
  • ¥15 微带串馈天线阵列每个阵元宽度计算
  • ¥15 keil的map文件中Image component sizes各项意思
  • ¥20 求个正点原子stm32f407开发版的贪吃蛇游戏
  • ¥15 划分vlan后,链路不通了?
  • ¥20 求各位懂行的人,注册表能不能看到usb使用得具体信息,干了什么,传输了什么数据