Как динамически обрабатывать пропущенные поля в ответе JSON в Go - PullRequest
0 голосов
/ 10 июля 2019

Я работаю над оболочкой Go для API и заметил, что два поля JSON остаются пустыми, когда у них нет данных.По сути, API возвращает набор информации по данному URL, и если он был посещен хотя бы один раз, все в порядке, и я получаю полный JSON, который затем Unmarshal в структуру:

{
   "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"
         }
      ]
   }
}

Этоструктура, которую я использую:

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"`
}

Когда дается новый URL, то все становится немного странным:

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

    ],
    "refs": [

    ]
  }
}

Как видите, поля "dev", "sys" и "bro" просто исчезают, потому что они не используются, и когда я пытаюсь разархивировать JSON в одну и ту же структуру, я получаю json: cannot unmarshal array into Go struct field Stats.device of type [...] Я пытался использовать две разные структуры для обработки обоих ответов, но я уверенчто есть способ справиться с ними изящно только с одним.Любая помощь будет оценена, спасибо!

1 Ответ

0 голосов
/ 10 июля 2019

Мне, наконец, удалось заставить его работать с безобразным обходным путем. Я изменил свою структуру на

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

Тогда я, наконец, могу демаршировать JSON в обоих случаях, но я получаю map[string]interface{} при передаче объекта и пустое interface{} при пропуске пустого массива. Чтобы устранить это несоответствие, я просто проверяю тип данных и принудительно использую промежуточное преобразование JSON, чтобы распаковать значение map[string]interface{} внутри пользовательской 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"`
}

Я использую следующие алгоритмы:

//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
}

Поскольку компилятор знает, что поля Devices и Refs interface{}, я не могу просто получить доступ к каким-либо методам после преобразования, поэтому я просто делаю приведение правильного типа, и все работает нормально. Например, если я хотел получить доступ к подструктуре Dev, это правильный путь:

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

Это некрасиво, но работает.

Большое спасибо за вашу помощь, я надеюсь, что этот метод избавит вас от головной боли!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...