程序员社区

json.Decoder的正确使用姿势

原文地址
这篇文章是我在研究golang的json.Decoder包时,我认为是一个bug的总结。

总的来说,我认为json.Decoder可能被误用了-这可能会导致意想不到的结果。在本文,我将说明如何安全的使用这个包而不是和大众介绍的那种。

谷歌:“json.decoder example golang"

使用上面的内容在谷歌上搜索,结果主要来自包括golang.org上的文档和medium.com的博客以及一些在stackoverflow的解答。

json.Decoder的正确使用姿势插图

庆幸的是golang.org的结果排在比较靠前-尽管根据golang的src源码来解析比较困难,但文档是比较全,最重要的是正确。(就我个人而言,我更希望在文档中有一些额外的背景来阐述我在其他文章中讨论的一些陷阱,但我跑题了)

我在stack overflow看到的一些回答是直接引用文档(因此也是正确的),但在谷歌上发现,其他可能是博客上拷来的内容是不正确的使用方式。

比如,在Medium博客等平台-我看到很多建议以如下方式使用json.Decoder;

func main() {
    jsonData := `{
"email":"abhirockzz@gmail.com",
"username":"abhirockzz",
"blogs":[
    {"name":"devto","url":"https://dev.to/abhirockzz/"},
    {"name":"medium","url":"https://medium.com/@abhishek1987/"}
]}`
    
    jsonDataReader := strings.NewReader(jsonData)
    decoder := json.NewDecoder(jsonDataReader)
    var profile Profile
    err := decoder.Decode(&profile)
    if err != nil {
        panic(err)
    }
    // ...
}

问题

从表面上看,这段代码看起来很好。我用golang环境上运行是没问题的。但是如果在json字符串后面增加一些信息:

package main

import (
  "encoding/json"
  "strings"
)

func main() {
  jsonData := `{
"email":"abhirockzz@gmail.com",
"username":"abhirockzz",
"blogs":[
  {"name":"devto","url":"https://dev.to/abhirockzz/"},
  {"name":"medium","url":"https://medium.com/@abhishek1987/"}
]}THIS IS INTENTIONALLY MALFORMED NOW`
  
  jsonDataReader := strings.NewReader(jsonData)
  decoder := json.NewDecoder(jsonDataReader)
  var profile map[string]interface{}
  err := decoder.Decode(&profile)
  if err != nil {
      panic(err)
  }
  // ...
}

还是能执行成功。这是咋回事?

正常情况下这段代码是不能正常运行的。给定的json字符串格式是不对的,根据正常的逻辑,代码应该panic。

简而言之,这就是整个问题所在。我在其他文章中阐述过:json.Decoder.Decode是为解析json数据流实现的,意思是它总会遍历json字符串直到找到满足的json关闭括号。我在这里使用满足这个词,因为它确实使用堆栈来跟踪内括号。

因此,为了检错误格式的json,我们实际必须在一个循环中运行这个逻辑-像这样:

package main

import (
    "encoding/json"
    "io"
    "strings"
)

func main() {
    jsonData := `{
"email":"abhirockzz@gmail.com",
"username":"abhirockzz",
"blogs":[
    {"name":"devto","url":"https://dev.to/abhirockzz/"},
    {"name":"medium","url":"https://medium.com/@abhishek1987/"}
]}THIS IS INTENTIONALLY MALFORMED NOW`

    jsonDataReader := strings.NewReader(jsonData)
    decoder := json.NewDecoder(jsonDataReader)
    var profile map[string]interface{}
    for {
        err := decoder.Decode(&profile)
        if err != nil {
            panic(err)
        }
        if err == io.EOF {
            break
        }
    }

    // ...
}

区别如下:


var profile map[string]interface{}
for {
    err := decoder.Decode(&profile)
    if err != nil {
        panic(err)
    }
    if err == io.EOF {
        break
    }
}
// ...

从golang文档中给出以下示例很好说明问题:

// This example uses a Decoder to decode a stream of distinct JSON values.
func ExampleDecoder() {
    const jsonStream = `
    {"Name": "Ed", "Text": "Knock knock."}
    {"Name": "Sam", "Text": "Who's there?"}
    {"Name": "Ed", "Text": "Go fmt."}
    {"Name": "Sam", "Text": "Go fmt who?"}
    {"Name": "Ed", "Text": "Go fmt yourself!"}
`
    type Message struct {
        Name, Text string
    }
    dec := json.NewDecoder(strings.NewReader(jsonStream))
    for {
        var m Message
        if err := dec.Decode(&m); err == io.EOF {
            break
        } else if err != nil {
            log.Fatal(err)
        }
        fmt.Printf("%s: %s\n", m.Name, m.Text)
    }
    // Output:
    // Ed: Knock knock.
    // Sam: Who's there?
    // Ed: Go fmt.
    // Sam: Go fmt who?
    // Ed: Go fmt yourself!
}

在这种用法中,我们从NewDecoder方法中创建了一个新的json.Decoder实例,然后不断循环,并尝试解码我们的JSON字符串,直到我们检测到字符串的结束(成功打破循环)符或发生错误。

我想在深入提下,当我在处理JSON数据流时,才会考虑使用json.Decoder。不管怎样,就是这个原则。在你要解析json字符串的时候,请记住这点。

注意事项

  • 我想指出的是,在我写这篇文章的时候,我并没有对其他人对golang的使用进行任何严格的分析。我的分析主要是来自于我对json.scanner和json.Decoder.Decode的源码的分析。
赞(0) 打赏
未经允许不得转载:IDEA激活码 » json.Decoder的正确使用姿势

一个分享Java & Python知识的社区