how to unmarshal json object if object is returning as empty string instead of empty struct

前端 未结 3 1691
渐次进展
渐次进展 2021-01-27 17:27

I\'m receiving some data as JSON, but if a object is empty, it does not return a empty struct but a empty string instead, and when unmarshaling, it returns an error.

So

相关标签:
3条回答
  • 2021-01-27 17:35

    You can have your item type implement the json.Unmarshaler interface.

    func (i *item) UnmarshalJSON(data []byte) error {
        if string(data) == `""` {
            return nil
        }
    
        type tmp item
        return json.Unmarshal(data, (*tmp)(i))
    }
    

    https://play.golang.org/p/1TrD57XULo9

    0 讨论(0)
  • 2021-01-27 17:35

    This might be a matter of taste, but "" is a string with zero length. Not an empty object. JSON uses null to describe something empty. This works:

    json3 := []byte(`{"title":"hello world","item":null}`)
        if err := json.Unmarshal(json3, &data3); err != nil {
            log.Println("3, err: ", err)
            return
    }
    

    As far as the documentation goes, omitempty is an encoding option:

    The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string.

    json.Unmarshal does not specify any use of the omitempty tag.

    If you don't have control over the input, use an interface type, a type switch and type assertion:

    type Store struct {
        Title string `json:"title,omitempty"`
        Item  item   `json:"item,omitempty"`
    }
    type item struct {
        Price float32 `json:"price,omitempty"`
        Kind  string  `json:"kind,omitempty"`
    }
    
    func unmarshal(js []byte) (*Store, error) {
        var data = struct { // Intermediate var for unmarshal
            Title string
            Item  interface{}
        }{}
    
        if err := json.Unmarshal(js, &data); err != nil {
            return nil, err
        }
    
        s := &Store{Title: data.Title}
    
        switch item := data.Item.(type) { // type switch
        case string, nil:
            return s, nil // Item remains empty
        case map[string]interface{}:
            p, ok := item["price"].(float64) // assertion
            if ok {
                s.Item.Price = float32(p)
            }
    
            s.Item.Kind, _ = item["kind"].(string) // _ prevents panic
            return s, nil
        default:
            return nil, errors.New("Unknown type")
        }
    
    }
    
    func main() {
        jsons := [][]byte{
            []byte(`{"title":"hello world","item":{"price":45.2,"kind":"fruit"}}`),
            []byte(`{"title":"hello world","item":{}}`),
            []byte(`{"title":"hello world","item":""}`),
            []byte(`{"title":"hello world","item":null}`),
        }
    
        for i, js := range jsons {
            data, err := unmarshal(js)
            if err != nil {
                log.Println("1, err: ", err)
                return
            }
            log.Printf("data %d: %+v\n", i, data)
        }
    }
    

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

    0 讨论(0)
  • 2021-01-27 17:53

    Create a type like type ItemOrEmptyString item

    And implement Unmarshal interface for it to handle your custom case.

    func(ies *ItemOrEmptyString)UnmarshalJSON(d []byte) error{
        var i item
        if string(d) == `""` {
           return nil
        }
        err := json.Unmarshal(d, &i)
        *ies  = ItemOrEmptyString(i)
        return err
    }
    

    Full code here

    0 讨论(0)
提交回复
热议问题