«

Golang中JSON遇到的坑如何解决

时间:2024-3-11 12:13     作者:韩俊     分类: Go语言


本篇内容主要讲解“Golang中JSON遇到的坑如何解决”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Golang中JSON遇到的坑如何解决”吧!

空指针会被解析成字符串"null"

type Person struct {
    Name string
    Age  int
}

func main() {
    var p *Person
    bytes, err := json.Marshal(p)
    checkError(err)
    fmt.Printf("len:%d, result:%s
", len(bytes), string(bytes))  // len:4, result:null
}

func checkError(err error) {
    if err != nil {
        fmt.Printf("err:%+v
", err)
    }
}

json.Marshal一个空指针的时候,得到的结果居然是"null"字符串,我以为是""或者报错。

还有个奇怪的坑

type Person struct {
    Name string
    Age  int
}

func main() {
    var p *Person
    s := `null`
    err := json.Unmarshal([]byte(s), &p)
    checkError(err)
    fmt.Printf("p:%+v
", p) // p:<nil>
}

这个居然不报错,而是得到空指针p

如果把s随便换成其他字符串

s := "abc"

,则报错:

invalid character 'a' looking for beginning of value

,之前我理解的是

null

对go来说应该跟

abc

没有差别,都是字符串。没想到他们是不一样的,下面来深究一下json.UnMarshal底层代码。


在UnMarshal之前它有个

checkValid

函数


func checkValid(data []byte, scan *scanner) error {
    scan.reset()
    for _, c := range data {
        scan.bytes++
        if scan.step(scan, c) == scanError {
            return scan.err
        }
    }
    if scan.eof() == scanError {
        return scan.err
    }
    return nil
}


checkValid

函数会check每一个字符,调用step函数,step初始值是

stateBeginValue


// stateBeginValue is the state at the beginning of the input.
func stateBeginValue(s *scanner, c byte) int {
    if isSpace(c) {
        return scanSkipSpace
    }
    switch c {
    case '{':
        s.step = stateBeginStringOrEmpty
        return s.pushParseState(c, parseObjectKey, scanBeginObject)
    case '[':
        s.step = stateBeginValueOrEmpty
        return s.pushParseState(c, parseArrayValue, scanBeginArray)
    case '"':
        s.step = stateInString
        return scanBeginLiteral
    case '-':
        s.step = stateNeg
        return scanBeginLiteral
    case '0': // beginning of 0.123
        s.step = state0
        return scanBeginLiteral
    case 't': // beginning of true
        s.step = stateT
        return scanBeginLiteral
    case 'f': // beginning of false
        s.step = stateF
        return scanBeginLiteral
    case 'n': // beginning of null
        s.step = stateN
        return scanBeginLiteral
    }
    if '1' <= c && c <= '9' { // beginning of 1234.5
        s.step = state1
        return scanBeginLiteral
    }
    return s.error(c, "looking for beginning of value")
}

有这么一段代码,这是处理第一个字符的,发现它对第一个字符是

n

有特殊处理并且设置下一个字符处理函数为stateN


// stateN is the state after reading `n`.
func stateN(s *scanner, c byte) int {
    if c == 'u' {
        s.step = stateNu
        return scanContinue
    }
    return s.error(c, "in literal null (expecting 'u')")
}

也就是下一个字符必须是

u

,再下一个字符处理函数为stateNu


// stateNu is the state after reading `nu`.
func stateNu(s *scanner, c byte) int {
    if c == 'l' {
        s.step = stateNul
        return scanContinue
    }
    return s.error(c, "in literal null (expecting 'l')")
}

也就是下一个字符必须是

l

,再下一个字符处理函数为stateNul


// stateNul is the state after reading `nul`.
func stateNul(s *scanner, c byte) int {
    if c == 'l' {
        s.step = stateEndValue
        return scanContinue
    }
    return s.error(c, "in literal null (expecting 'l')")
}

也就是下一个字符必须是

l

,再下一个字符处理函数为stateEndValue。


可见

checkValid

函数对true,false等都有特殊处理。使用时需要注意。


对于json.Marshal函数,通过调试发现它对空指针也有特殊处理

type ptrEncoder struct {
    elemEnc encoderFunc
}

func (pe ptrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
    if v.IsNil() {
        e.WriteString("null")
        return
    }
    if e.ptrLevel++; e.ptrLevel > startDetectingCyclesAfter {
        // We're a large number of nested ptrEncoder.encode calls deep;
        // start checking if we've run into a pointer cycle.
        ptr := v.Interface()
        if _, ok := e.ptrSeen[ptr]; ok {
            e.error(&UnsupportedValueError{v, fmt.Sprintf("encountered a cycle via %s", v.Type())})
        }
        e.ptrSeen[ptr] = struct{}{}
        defer delete(e.ptrSeen, ptr)
    }
    pe.elemEnc(e, v.Elem(), opts)
    e.ptrLevel--
}

如果是空指针则返回字符串"null",并且不会报错。

int类型会被解析成float64

type Person struct {
    Name string
    Age  int
}

func main() {
    p := &Person{
        Name: "text",
        Age:  18,
    }

    bytes, err := json.Marshal(p)
    checkError(err)

    pMap := make(map[string]interface{})
    err = json.Unmarshal(bytes, &pMap)
    checkError(err)
    for k, v := range pMap {
        fmt.Printf("k:%s,v:%+v, vtype:%v
", k, v, reflect.TypeOf(v))
    }
}

func checkError(err error) {
    if err != nil {
        fmt.Printf("err:%+v
", err)
    }
}

结果

k:Name,v:text, vtype:string
k:Age,v:18, vtype:float64

显然,Age类型变成了float64。会造成什么问题呢?当int大小超过6位的时候就变成了科学计数法 比如Age=1234567, 结果为

k:Name,v:text, vtype:string
k:Age,v:1.234567e+06, vtype:float64

这个时候如果直接将map更新到db,原本是int类型的字段变成了float类型,就报错了

标签: golang

热门推荐