Go JSON 技巧

相对于很多的语言来说, Go 的 JSON 解析可谓简单至极.

问题

通常情况下, 我们在 Go 中经常这样进行 JSON 的解码:

package main

import "encoding/json"

// jsonText comes from http://json.org/example.html
var jsonText = []byte(`
{
   "glossary":{
      "title":"example glossary",
      "GlossDiv":{
         "title":"S",
         "GlossList":{
            "GlossEntry":{
               "ID":"SGML",
               "SortAs":"SGML",
               "GlossTerm":"Standard Generalized Markup Language",
               "Acronym":"SGML",
               "Abbrev":"ISO 8879:1986",
               "GlossDef":{
                  "para":"A meta-markup language, used to create markup languages such as DocBook.",
                  "GlossSeeAlso":[
                     "GML",
                     "XML"
                  ]
               },
               "GlossSee":"markup"
            }
         }
      }
   }
}`)

type glossary struct {
	Glossary struct {
		Title    string `json:"title"`
		GlossDiv struct {
			Title     string `json:"title"`
			GlossList struct {
				GlossEntry struct {
					ID        string `json:"ID"`
					SortAs    string `json:"SortAs"`
					GlossTerm string `json:"GlossTerm"`
					Acronym   string `json:"Acronym"`
					Abbrev    string `json:"Abbrev"`
					GlossDef  struct {
						Para         string   `json:"para"`
						GlossSeeAlso []string `json:"GlossSeeAlso"`
					} `json:"GlossDef"`
					GlossSee string `json:"GlossSee"`
				} `json:"GlossEntry"`
			} `json:"GlossList"`
		} `json:"GlossDiv"`
	} `json:"glossary"`
}

func main() {
	var g glossary
	json.Unmarshal(jsonText, &g)
}

这样的解码对于我们日常使用好像也没什么问题, 起码能用 ? 对于一段 JSON, 我们解码的时候未必需要立即解码所有的部分, 什么意思呢 ?

拿上面的例子代码来说, 我们解码 jsonText , 可能仅需要马上使用 Title 和 GlossDiv.Title . 那么对于这种情况我们怎么做合适呢 ?

package main

import "encoding/json"

// jsonText comes from http://json.org/example.html
var jsonText = []byte(`
{
   ... // 此处省略, 同上
}`)

type glossarySectional struct {
	Glossary struct {
		Title    string `json:"title"`
		GlossDiv struct {
			Title     string          `json:"title"`
			GlossList json.RawMessage `json:"GlossList"` // diff: delay JSON decoding
		} `json:"GlossDiv"`
	} `json:"glossary"`
}

func main() {
	var g glossarySectional
	json.Unmarshal(jsonText, &g)
}

没错, 魔法就在 GlossList json.RawMessage . 我们看相关文档怎么说:

RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding.

一目了然, RawMessage 起到了延迟解码一个 JSON 值的作用. 那么你可能会说, 这有啥用呢 ?

这对于普通的解码可能问题不大, 但是对于一些像消息传递(Kafka 这种), 细微的延迟可能会造成很大的影响. 我们可以通过简单的 benchmark 测试一下这细微的差别:

// 其他代码略 ... 完整代码参见: http://bit.ly/2skxY9L .

func benchmarkJSONUnmarshal(f func(), b *testing.B) {
	for n := 0; n < b.N; n++ {
		f()
	}
}

func BenchmarkJSONUnmarshal_0(b *testing.B) {
	benchmarkJSONUnmarshal(func() {
		var g glossary
		json.Unmarshal(jsonText, &g)
	}, b)
}

func BenchmarkJSONUnmarshal_1(b *testing.B) {
	benchmarkJSONUnmarshal(func() {
		var g glossarySectional
		json.Unmarshal(jsonText, &g)
	}, b)
}

我们通过运行 go test -run=NONE -bench=. ./… 可以得出(不同环境有略微差别):

BenchmarkJSONUnmarshal_0-8   	  200000	     10565 ns/op
BenchmarkJSONUnmarshal_1-8   	  200000	      7699 ns/op

差别幅度:

benchmark                    old ns/op     new ns/op     delta
BenchmarkJSONUnmarshal-8     10298         7591          -26.29%

可以看得出这个差别还是很大的, 特别是当 JSON 本身体量很大的时候.

结论

对于一些关乎性能的 JSON 解析的处理, 我们可以通过 json.RawMessage 进行性能的提升.

comments powered by Disqus

2018-02-10


On this page: