Golang: JSON: How do I unmarshal array of strings into []int64

前端 未结 2 1202
粉色の甜心
粉色の甜心 2021-02-11 02:27

Golang encoding/json package lets you use ,string struct tag in order to marshal/unmarshal string values (like \"309230\") into int6

相关标签:
2条回答
  • 2021-02-11 02:37

    For anyone interested, I found a solution using a custom type having MarshalJSON() and UnmarshalJSON() methods defined.

    type Int64StringSlice []int64
    
    func (slice Int64StringSlice) MarshalJSON() ([]byte, error) {
        values := make([]string, len(slice))
        for i, value := range []int64(slice) {
            values[i] = fmt.Sprintf(`"%v"`, value)
        }
    
        return []byte(fmt.Sprintf("[%v]", strings.Join(values, ","))), nil
    }
    
    func (slice *Int64StringSlice) UnmarshalJSON(b []byte) error {
        // Try array of strings first.
        var values []string
        err := json.Unmarshal(b, &values)
        if err != nil {
            // Fall back to array of integers:
            var values []int64
            if err := json.Unmarshal(b, &values); err != nil {
                return err
            }
            *slice = values
            return nil
        }
        *slice = make([]int64, len(values))
        for i, value := range values {
            value, err := strconv.ParseInt(value, 10, 64)
            if err != nil {
                return err
            }
            (*slice)[i] = value
        }
        return nil
    }
    

    The above solution marshals []int64 into JSON string array. Unmarshaling works from both JSON string and integer arrays, ie.:

    {"bars": ["1729382256910270462", "309286902808622", "23"]}
    
    {"bars": [1729382256910270462, 309286902808622, 23]}
    

    See example at https://play.golang.org/p/BOqUBGR3DXm

    0 讨论(0)
  • 2021-02-11 02:58

    As you quoted from json.Marshal(), the ,string option only applies to specific types, namely:

    The "string" option signals that a field is stored as JSON inside a JSON-encoded string. It applies only to fields of string, floating point, integer, or boolean types.

    You want it to work with a slice, but that is not supported by the json package.

    If you still want this functionality, you have to write your custom marshaling / unmarshaling logic.

    What you presented works, but it is unnecessarily complex. This is because you created your custom logic on slices, but you only want this functionality on individual elements of the slices (arrays). You don't want to change how an array / slice (as a sequence of elements) is rendered or parsed.

    So a much simpler solution is to only create a custom "number" type producing this behavior, and elements of slices of this custom type will behave the same.

    Our custom number type and the marshaling / unmarshaling logic:

    type Int64Str int64
    
    func (i Int64Str) MarshalJSON() ([]byte, error) {
        return json.Marshal(strconv.FormatInt(int64(i), 10))
    }
    
    func (i *Int64Str) UnmarshalJSON(b []byte) error {
        // Try string first
        var s string
        if err := json.Unmarshal(b, &s); err == nil {
            value, err := strconv.ParseInt(s, 10, 64)
            if err != nil {
                return err
            }
            *i = Int64Str(value)
            return nil
        }
    
        // Fallback to number
        return json.Unmarshal(b, (*int64)(i))
    }
    

    And that's all!

    The type using it:

    type Foo struct {
        Bars []Int64Str `json:"bars"`
    }
    

    Testing it the same way as you did yields the same result. Try it on the Go Playground.

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