在 Unmarshal 期间处理不同类型的参数

Posted

技术标签:

【中文标题】在 Unmarshal 期间处理不同类型的参数【英文标题】:Dealing with different types of parameters during Unmarshal 【发布时间】:2021-04-04 15:36:00 【问题描述】:

我正在使用我的 Fritz!Box 路由器中的一些 API,我想在一个体面的结构中解组 json 响应,只需要找到一个好的方法来做到这一点。

有时在 API 响应中 WLan 参数是布尔值,有时是这种类型的对象

// WLan contains info about the Wireless Lan
type WLan struct 
    Txt     string `json:"txt"`
    Led     string `json:"led"`
    Title   string `json:"title"`
    Link    string `json:"link"`
    Tooltip string `json:"tooltip"`

如果您需要有关代码的更多信息,可以使用github repo。

我需要添加布尔 wlan 参数,我尝试复制“数据”结构并更改名称,但该解决方案对我来说听起来很糟糕。

Wlan 包含在这个结构中:

// Data contains data about the Fritz!Box
type Data struct 
    NasLink          string    `json:"naslink"`
    FritzOS          FritzOS   `json:"fritzos"`
    Webdav           int       `json:"webdav,string"`
    Manual           string    `json:"MANUAL_URL"`
    Language         string    `json:"language"`
    AVM              string    `json:"AVM_URL"`
    USBConnect       string    `json:"usbconnect"`
    Foncalls         Foncalls  `json:"foncalls"`
    ***              ***       `json:"***"`
    Internet         Internet  `json:"internet"`
    DSL              DSL       `json:"dsl"`
    ServicePortalURL string    `json:"SERVICEPORTAL_URL"`
    Comfort          Comfort   `json:"comfort"`
    Changelog        Changelog `json:"changelog"`
    TamCalls         TamCalls  `json:"tamcalls"`
    Lan              External  `json:"lan"`
    USB              External  `json:"usb"`
    FonNum           External  `json:"fonnum"`
    NewsURL          string    `json:"NEWSLETTER_URL"`
    Net              Net       `json:"net"`
    Dect             External  `json:"dect"`
    WLan             WLan      `json:"wlan"`
  //Wlan             bool      `json:"wlan"` # This is the other "case"

【问题讨论】:

这是在另一个结构中使用吗?如果是这样,也包括封闭结构。有多种方法可以解决此问题,但您需要在解组完成后阐明您想要什么。 是的,WLAN 包含在数据中。数据将“wlan”名称解组为 WLan,但有时不是 json 对象,而是一个 bool 值。无论如何你可以在 github 上看到完整的源代码。 【参考方案1】:

您可以实现json.Unmarshalerjson.Marshaler 接口。

type WLan struct 
    Bool    *bool  `json:"-"`
    Txt     string `json:"txt"`
    Led     string `json:"led"`
    Title   string `json:"title"`
    Link    string `json:"link"`
    Tooltip string `json:"tooltip"`


// implements json.Unmarshaler
func (w *WLan) UnmarshalJSON(data []byte) error 
    if len(data) > 0 && (data[0] == 't' || data[0] == 'f')  // seems to be a bool
        w.Bool = new(bool)
        return json.Unmarshal(data, w.Bool)
    
    if len(data) > 1 && data[0] == '' && data[len(data)-1] == ''  // it's an object
        // type W and the conversion (*W)(w) are required to
        // prevent encoding/json from invoking the UnmarshalJSON
        // method recursively causing a stack overflow
        type W WLan
        return json.Unmarshal(data, (*W)(w))
    
    return nil // or error, up to you


// implements json.Marshaler
func (w WLan) MarshalJSON() ([]byte, error) 
    if w.Bool != nil 
        return json.Marshal(*w.Bool)
    
    // Same as with UnmarshalJSON, type W and the conversion W(w) are 
    // required to prevent encoding/json from invoking the MarshalJSON
    // method recursively causing a stack overflow
    type W WLan
    return json.Marshal(W(w))

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

【讨论】:

【参考方案2】:

我不知道这是否是一个好的解决方案,我还是新手,但无论如何,您可以使用 json.RawMessage 并将 wlan 属性的解组“延迟”到两个单独的结构字段之一中.例如:

package main

import (
    "encoding/json"
    "fmt"
)

// Data contains data about the Fritz!Box. (other fields omitted for brevity)
type Data struct 
    Language string           `json:"language"`
    NewsURL  string           `json:"NEWSLETTER_URL"`
    WLanRaw  *json.RawMessage `json:"wlan"`
    WLanBool bool             `json:"-"`
    WLanInfo *WLanInfo        `json:"-"`


// WLanInfo contains infos about the Wireless Lan
type WLanInfo struct 
    Txt     string `json:"txt"`
    Led     string `json:"led"`
    Title   string `json:"title"`
    Link    string `json:"link"`
    Tooltip string `json:"tooltip"`


func UnmarshalData(raw []byte, data *Data) error 
    if err := json.Unmarshal(raw, data); err != nil 
        return err
    
    switch string(*data.WLanRaw) 
    case "true", "false":
        json.Unmarshal(*data.WLanRaw, &data.WLanBool)
    default:
        if err := json.Unmarshal(*data.WLanRaw, &data.WLanInfo); err != nil 
            return err
        
    
    return nil


func main() 
    jsonBool := []byte(`

    "language": "it",
    "NEWSLETTER_URL": "https://example.com/news",
    "wlan": true
`)

    jsonInfo := []byte(`

    "language": "it",
    "NEWSLETTER_URL": "https://example.com/news",
    "wlan": 
        "txt": "footxt",
        "led": "fooled",
        "title": "hello",
        "link": "bar",
        "tooltip": "baz"
    
`)

    // error handling omitted
    var dataBool Data
    UnmarshalData(jsonBool, &dataBool)
    fmt.Printf("%+v\n\n", dataBool)

    var dataInfo Data
    UnmarshalData(jsonInfo, &dataInfo)
    fmt.Printf("%+v %+v\n", dataInfo, dataInfo.WLanInfo)


$ go build fritz.go
$ ./fritz
Language:it NewsURL:https://example.com/news WLanRaw:0xc0000a4060 WLanBool:true WLanInfo:<nil>

Language:it NewsURL:https://example.com/news WLanRaw:0xc0000a4080 WLanBool:false WLanInfo:0xc0000b0000 &Txt:footxt Led:fooled Title:hello Link:bar Tooltip:baz
$

【讨论】:

可能是一个解决方案,但是当有人检查“bool”属性时会导致问题,因为它总是错误的 @LucaDametto 当wlan 属性是 json 中的对象时,它是“总是false”。好吧,只有当WLanInfo == nil 时才应该检查WLanBool。有点丑,我知道... 嗯,可以解决。我会尝试实现你的例子。

以上是关于在 Unmarshal 期间处理不同类型的参数的主要内容,如果未能解决你的问题,请参考以下文章

Unmarshal中的动态类型

golang Marshal / Unmarshal将不同的JSON对象转换为Go结构

(un)signed short int (C)

golang Marshal和Unmarshal处理json数据

具有不同参数类型和存储类型的回调

Golang解析json的特殊情况处理