golang如何解析结构不确定的Json

发布于 作者 shisongran2 条评论

在业务中,我们一般会尝试定义Struct映射到固定的Json Struct,利用json.Marshal和json.Unmarshal便利的实现序列化。而在业务中,我们难免会遇到需要解析结构不确定的Json的情况,使用起来有各种不便。

下面介绍一种解析不确定Json的方式:

如何解析

json.Unmarshal在进行json解析的时,我们可以传入了interface{}来涵盖所有的Json结构,以处理不定结构Json的解析。

举例:

JavaScript
{
    "tagA" : "json string",     // json string
    "tagB" : 1024,  // json number
    "tagC" : null,  // null
    "tagD" : {
        "tagE" : 2147483648, // big int 2^31
        "tagF" : 3.14159265358979, // float64
    }
    "tagG" : [
        "json array",
       1024,
        {"tagH" : "json object"}
    ] // json array
}

类似上面的Json,我们使用interface{}直接进行解析。

Go
jsonString := "{...}" // 上述Json
resultInterface := interface{}{}
    
// 不推荐使用json.Unmarshal
decoder := json.NewDecoder(bytes.NewBuffer(response.Body)) 
decoder.UseNumber() // 此处能够保证bigint的精度
decoder.Decode(&resultInterface)

如果不出意外,interface{}中存储的数据结构如下:

Go
var resultInterface interface{} = map[string]interface{}{
    "tagA": "json string", // string
    "tagB": "1024",        // json.Number
    "tagC": nil,           // nil
    "tagD": map[string]interface{}{ // map[string]interface{}
        "tagE": "2147483648",       // json.Number
        "tagF": "3.14159265358979", // json.Number
    },
    "tagG": []interface{}{ // slice
        "json array", // string
        "1024",       // json.Number
        map[string]interface{}{ // map[string]interface{}
            "tagH": "json object",
        },
    },
}

注释是每个结构在interface{}中的实际数据类型。应该能够注意到json.Number这个类型,这是为了处理数值类型而定义的数据,直接存储json中数值的字面数据,以保证精度。该类型有三个接口,可以分别拿到int64、float64或string类型的值。

如何序列化

既然可以通过interface{}来解析,自然也可以使用interface{}来组成结构灵活可变的Json。简单讲就是map[string]interface{}将被解析为json object,[]interface{}将被解析为json array,下面这张表里面列举了一些解析的映射,方便对应。

golang type json type
int/int8/int16/int32/int64 json number
uint/uint8/uint16/uint32/uint64 json number
map[string]interface{} json object
[]interface{} json array
float/float64 json number with scientific notation
json.Number json number

值得注意的是,如果需要序列化float类型的数据,建议先将数据转化为json.Number类型,然后再进行json.Marshal,否则float会被序列化为科学计数法。

Golang底层的实现方式

追根朔源,下面说说传入interface{}类型后,golang是如何进行解析的。

不管是直接调用json.Unmarshal还是通过json.NewDecoder解析,都需要调用到json.Decoder类型中的Decode(v interface{})函数,进而可以看到具体解析Json功能在json.decodeState.value(v reflect.Value)中。

Go
func (d *decodeState) value(v reflect.Value) {
    if !v.IsValid() {
       ............. // 错误处理
    }

    switch op := d.scanWhile(scanSkipSpace); op {
    default:
        d.error(errPhase)

    case scanBeginArray:
        d.array(v)

    case scanBeginObject:
        d.object(v)

    case scanBeginLiteral:
        d.literal(v)
    }
}

针对不同Json解析后的字段,有三种不同的分支,有两种明显针对json object、json array进行解析,另外一种是针对json number/string等不用二次解析的类型。下面三中类型,针对interface{}的分别如下:

  • json object
Go
func (d *decodeState) object(v reflect.Value) {
        
        ......
    
    // 如果是interface类型
    if v.Kind() == reflect.Interface && v.NumMethod() == 0 {
        v.Set(reflect.ValueOf(d.objectInterface()))
        return
    }
    
        ......
    
}
    
// 返回
func (d *decodeState) objectInterface() map[string]interface{} {
    .......
}

可以看到直接将内容解析成了map[string]interface{}

  • json array
Go
func (d *decodeState) array(v reflect.Value) {
        
        ......
        
    switch v.Kind() {
    case reflect.Interface:
        if v.NumMethod() == 0 {
            v.Set(reflect.ValueOf(d.arrayInterface()))
            return
        }
        
        ......
            
    }
}
    

与json object原理相似,直接解析为了[]interface{}

  • json literal

此处值得注意的点是针对json number的处理上。

Go
func (d *decodeState) convertNumber(s string) (interface{}, error) {
    if d.useNumber {
        return Number(s), nil
    }
    f, err := strconv.ParseFloat(s, 64)
    if err != nil {
        return nil, &UnmarshalTypeError{"number " + s, reflect.TypeOf(0.0), int64(d.off)}
    }
    return f, nil
}

可以看到针对interface{}如果没有使用d.useNumber设置,那么默认是全部作为float来处理的,此处可能造成精度损失。这就是上面调用decoder.UseNumber()的意义。

2 则回应给 golang如何解析结构不确定的Json

发表评论

电子邮件地址不会被公开。 必填项已用*标注