douyi3307 2019-07-09 21:21
浏览 217
已采纳

如何在Go中动态处理JSON响应中的缺失字段

I'm working on a Go wrapper for an API and I noticed that two of the JSON fields stay empty when they don't have any data. Basically the API returns a set of information on a given url, and if it was visited at least once, everything is okay and I get a full json that I then Unmarshal into a struct:

{
   "stats":{
      "status":1,
      "date":"09.07.2019",
      "title":"Test",
      "devices":{
         "dev":[
            {
               "tag":"Desktop"
            }
         ],
         "sys":[
            {
               "tag":"GNU/Linux "
            },
            {
               "tag":"Windows 10"
            }
         ],
         "bro":[
            {
               "tag":"Firefox 67.0"
            },
            {
               "tag":"Chrome 62.0"
            }
         ]
      },
      "refs":[
         {
            "link":"www.google.com"
         }
      ]
   }
}

This is the struct I'm using:

type Stats struct {
    Stats struct {
        Status  int    `json:"status"`
        Date    string `json:"date"`
        Title   string `json:"title"`
        Devices struct {
            Dev []struct {
                Tag string `json:"tag"`
            } `json:"dev"`
            Sys []struct {
                Tag string `json:"tag"`
            } `json:"sys"`
            Bro []struct {
                Tag string `json:"tag"`
            } `json:"bro"`
        } `json:"devices"`
        Refs []struct {
            Link string `json:"link"`
        } `json:"refs"`
    } `json:"stats"`
}

When a new url is given, then things become a little bit weird:

{
  "stats": {
    "status": 1,
    "date": "09.07.2019",
    "title": "Test2",
    "devices": [

    ],
    "refs": [

    ]
  }
}

As you can see, the fields "dev", "sys" and "bro" just disappear because they're not used and when I try to Unmarshal the JSON into the same struct I get json: cannot unmarshal array into Go struct field Stats.device of type [...] I tried to use two different structs to handle both the responses but I'm sure that there's a way to handle them gracefully with just one. Any help would be appreciated, thanks!

  • 写回答

1条回答 默认 最新

  • dp198879 2019-07-10 15:47
    关注

    I finally managed to make it work with an ugly workaround. I changed my struct to

    type Stats struct {
        Status     int         `json:"status"`
        Date       string      `json:"date"`
        Title      string      `json:"title"`
        Devices    interface{} `json:"devices"`
        Refs       interface{} `json:"refs"`
    }
    

    Then I can finally Unmarshal the JSON in both cases, but I get a map[string]interface{} when an object is passed and an empty interface{} when an empty array is passed. In order to fix this inconsistency, I simply check for the data type and force the use of a JSON intermediate conversion in order to unpack the map[string]interface{} value inside a custom Devices struct:

    // Devices contains devices information
    type Devices struct {
        Dev []struct {
            Tag    string `json:"tag"`
            Clicks string `json:"clicks"`
        } `json:"dev"`
        Sys []struct {
            Tag    string `json:"tag"`
            Clicks string `json:"clicks"`
        } `json:"sys"`
        Bro []struct {
            Tag    string `json:"tag"`
            Clicks string `json:"clicks"`
        } `json:"bro"`
    }
    

    The algorithms I use are the following:

    //ForceDevicesToRightType uses a json conversion as intermediary for filling the Stats.Devices
    // struct with map[string]interface{} values
    func ForceDevicesToRightType(dev interface{}) (Devices, error) {
        temp, err := json.Marshal(dev)
        if err != nil {
            return Devices{}, err
        }
        // Use a temporary variable of the right type
        var devices Devices
        err = json.Unmarshal(temp, &devices)
        if err != nil {
            return Devices{}, err
        }
    
        return devices, nil
    }
    
    // ForceRefsToRightType uses a json conversion as intermediary for filling the Stats.Refs
    // struct with map[string]interface{} values
    func ForceRefsToRightType(refs interface{}) (Refs, error) {
        temp, err := json.Marshal(refs)
        if err != nil {
            return Refs{}, err
        }
        // Use a temporary variable of the right type
        var references Refs
        err = json.Unmarshal(temp, &references)
        if err != nil {
            return Refs{}, err
        }
    
        return references, nil
    }
    

    Since the compiler knows that both Devices and Refs fields are interface{} I cannot simply access any methods after the conversion, so I simply make a cast of the right type and everything works fine. For example, if I wanted to access the Dev sub-struct, this is the proper way:

    y, _ := GetStats()
    fmt.Println(y.Devices.(Devices).Dev)
    

    It's ugly, but it works.

    Thank you very much for your help, I hope that this method will save you an headache!

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥50 汇编语言除法溢出问题
  • ¥65 C++实现删除N个数据列表共有的元素
  • ¥15 Visual Studio问题
  • ¥15 state显示变量是字符串形式,但是仍然红色,无法引用,并显示类型不匹配
  • ¥20 求一个html代码,有偿
  • ¥100 关于使用MATLAB中copularnd函数的问题
  • ¥20 在虚拟机的pycharm上
  • ¥15 jupyterthemes 设置完毕后没有效果
  • ¥15 matlab图像高斯低通滤波
  • ¥15 针对曲面部件的制孔路径规划,大家有什么思路吗