本文的目的是介绍在UnmarshalJSON函数中调用json.Unmarshal而不引起堆栈溢出的详细情况,我们将通过专业的研究、有关数据的分析等多种方式,同时也不会遗漏关于for-loop与jso
本文的目的是介绍在UnmarshalJSON函数中调用json.Unmarshal而不引起堆栈溢出的详细情况,我们将通过专业的研究、有关数据的分析等多种方式,同时也不会遗漏关于for-loop 与 json.Unmarshal 性能分析概要、Go json Marshal & UnMarshal 的一点小 trick、Go json 自定义 Unmarshal 避免判断 nil、go json 解析 Marshal 和 Unmarshal的知识。
本文目录一览:- 在UnmarshalJSON函数中调用json.Unmarshal而不引起堆栈溢出
- for-loop 与 json.Unmarshal 性能分析概要
- Go json Marshal & UnMarshal 的一点小 trick
- Go json 自定义 Unmarshal 避免判断 nil
- go json 解析 Marshal 和 Unmarshal
在UnmarshalJSON函数中调用json.Unmarshal而不引起堆栈溢出
我想执行一些其他步骤来初始化实现中的数据结构UnmarshalJSON
。json.Unmarshal(b,type)
在该实现内调用自然会导致堆栈溢出。
JSON解码器会不断尝试查找,如果有自定义UnmarshalJSON
实现,然后再次调用json.Unmarshal
。
还有另一种方法吗?只是调用基本的默认实现而不会导致此问题?
答案1
小编典典避免这种情况/避免这种情况发生的一种简单且常见的方法是type
使用关键字创建一个新类型,并使用类型转换来传递该类型的值(该值可能是您的原始值,因为新的类型具有原始类型作为其基础类型)。
之所以type
可行,是因为关键字创建了一个新类型,并且该新类型将具有零个方法(它不会“继承”基础类型的方法)。
这会产生一些运行时开销吗?编号。从规格报价:转换:
特定规则适用于数字类型之间或字符串类型之间的(非恒定)转换。这些转换可能会更改的表示形式
x
并产生运行时成本。
所有其他转换只会更改类型,而不会更改的表示形式x
。
让我们来看一个例子。我们有一个Person
带有数字的类型Age
,我们要确保Age
不能为负数(小于0
)。
type Person struct { Name string `json:"name"` Age int `json:"age"`}func (p *Person) UnmarshalJSON(data []byte) error { type person2 Person if err := json.Unmarshal(data, (*person2)(p)); err != nil { return err } // Post-processing after unmarshaling: if p.Age < 0 { p.Age = 0 } return nil}
测试它:
var p *Personfmt.Println(json.Unmarshal([]byte(`{"name":"Bob","age":10}`), &p))fmt.Println(p)fmt.Println(json.Unmarshal([]byte(`{"name":"Bob","age":-1}`), &p))fmt.Println(p)
输出(在Go Playground上尝试):
<nil>&{Bob 10}<nil>&{Bob 0}
当然,相同的技术也适用于自定义封送处理(MarshalJSON()
):
func (p *Person) MarshalJSON() ([]byte, error) { // Pre-processing before marshaling: if p.Age < 0 { p.Age = 0 } type person2 Person return json.Marshal((*person2)(p))}
测试它:
p = &Person{"Bob", 10}fmt.Println(json.NewEncoder(os.Stdout).Encode(p))p = &Person{"Bob", -1}fmt.Println(json.NewEncoder(os.Stdout).Encode(p))
输出(在相同的Go Playground示例中):
{"name":"Bob","age":10}<nil>{"name":"Bob","age":0}<nil>
一个非常相似的问题是,当您String()string
为fmt
包定义自定义文本表示方法时,您想使用所修改的默认字符串表示形式。
for-loop 与 json.Unmarshal 性能分析概要
原文地址:for-loop 与 json.Unmarshal 性能分析概要
前言
在项目中,常常会遇到循环交换赋值的数据处理场景,尤其是 RPC,数据交互格式要转为 Protobuf,赋值是无法避免的。一般会有如下几种做法:
- for
- for range
- json.Marshal/Unmarshal
这时候又面临 “选择困难症”,用哪个好?又想代码量少,又担心性能有没有影响啊...
为了弄清楚这个疑惑,接下来将分别编写三种使用场景。来简单看看它们的性能情况,看看谁更 “好”
功能代码
...
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Avatar string `json:"avatar"`
Type string `json:"type"`
}
type AgainPerson struct {
Name string `json:"name"`
Age int `json:"age"`
Avatar string `json:"avatar"`
Type string `json:"type"`
}
const MAX = 10000
func InitPerson() []Person {
var persons []Person
for i := 0; i < MAX; i++ {
persons = append(persons, Person{
Name: "EDDYCJY",
Age: i,
Avatar: "https://github.com/EDDYCJY",
Type: "Person",
})
}
return persons
}
func ForStruct(p []Person, count int) {
for i := 0; i < count; i++ {
_, _ = i, p[i]
}
}
func ForRangeStruct(p []Person) {
for i, v := range p {
_, _ = i, v
}
}
func JsonToStruct(data []byte, againPerson []AgainPerson) ([]AgainPerson, error) {
err := json.Unmarshal(data, &againPerson)
return againPerson, err
}
func JsonIteratorToStruct(data []byte, againPerson []AgainPerson) ([]AgainPerson, error) {
var jsonIter = jsoniter.ConfigCompatibleWithStandardLibrary
err := jsonIter.Unmarshal(data, &againPerson)
return againPerson, err
}
测试代码
...
func BenchmarkForStruct(b *testing.B) {
person := InitPerson()
count := len(person)
b.ResetTimer()
for i := 0; i < b.N; i++ {
ForStruct(person, count)
}
}
func BenchmarkForRangeStruct(b *testing.B) {
person := InitPerson()
b.ResetTimer()
for i := 0; i < b.N; i++ {
ForRangeStruct(person)
}
}
func BenchmarkJsonToStruct(b *testing.B) {
var (
person = InitPerson()
againPersons []AgainPerson
)
data, err := json.Marshal(person)
if err != nil {
b.Fatalf("json.Marshal err: %v", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
JsonToStruct(data, againPersons)
}
}
func BenchmarkJsonIteratorToStruct(b *testing.B) {
var (
person = InitPerson()
againPersons []AgainPerson
)
data, err := json.Marshal(person)
if err != nil {
b.Fatalf("json.Marshal err: %v", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
JsonIteratorToStruct(data, againPersons)
}
}
测试结果
BenchmarkForStruct-4 500000 3289 ns/op 0 B/op 0 allocs/op
BenchmarkForRangeStruct-4 200000 9178 ns/op 0 B/op 0 allocs/op
BenchmarkJsonToStruct-4 100 19173117 ns/op 2618509 B/op 40036 allocs/op
BenchmarkJsonIteratorToStruct-4 300 4116491 ns/op 3694017 B/op 30047 allocs/op
从测试结果来看,性能排名为:for < for range < json-iterator < encoding/json。接下来我们看看是什么原因导致了这样子的排名?
性能对比
for-loop
在测试结果中,for range
在性能上相较 for
差。这是为什么呢?在这里我们可以参见 for range
的 实现,伪实现如下:
for_temp := range
len_temp := len(for_temp)
for index_temp = 0; index_temp < len_temp; index_temp++ {
value_temp = for_temp[index_temp]
index = index_temp
value = value_temp
original body
}
通过分析伪实现,可得知 for range
相较 for
多做了如下事项
Expression
RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .
在循环开始之前会对范围表达式进行求值,多做了 “解” 表达式的动作,得到了最终的范围值
Copy
...
value_temp = for_temp[index_temp]
index = index_temp
value = value_temp
...
从伪实现上可以得出,for range
始终使用值拷贝的方式来生成循环变量。通俗来讲,就是在每次循环时,都会对循环变量重新分配
小结
通过上述的分析,可得知其比 for
慢的原因是 for range
有额外的性能开销,主要为值拷贝的动作导致的性能下降。这是它慢的原因
那么其实在 for range
中,我们可以使用 _
和 T[i]
也能达到和 for
差不多的性能。但这可能不是 for range
的设计本意了
json.Marshal/Unmarshal
encoding/json
json 互转是在三种方案中最慢的,这是为什么呢?
众所皆知,官方的 encoding/json
标准库,是通过大量反射来实现的。那么 “慢”,也是必然的。可参见下述代码:
...
func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
...
switch t.Kind() {
case reflect.Bool:
return boolEncoder
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return intEncoder
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return uintEncoder
case reflect.Float32:
return float32Encoder
case reflect.Float64:
return float64Encoder
case reflect.String:
return stringEncoder
case reflect.Interface:
return interfaceEncoder
case reflect.Struct:
return newStructEncoder(t)
case reflect.Map:
return newMapEncoder(t)
case reflect.Slice:
return newSliceEncoder(t)
case reflect.Array:
return newArrayEncoder(t)
case reflect.Ptr:
return newPtrEncoder(t)
default:
return unsupportedTypeEncoder
}
}
既然官方的标准库存在一定的 “问题”,那么有没有其他解决方法呢?目前在社区里,大多为两类方案。如下:
- 预编译生成代码(提前确定类型),可以解决运行时的反射带来的性能开销。缺点是增加了预生成的步骤
- 优化序列化的逻辑,性能达到最大化
接下来的实验,我们用第二种方案的库来测试,看看有没有改变。另外也推荐大家了解如下项目:
- json-iterator/go
- mailru/easyjson
- pquerna/ffjson
json-iterator/go
目前社区较常用的是 json-iterator/go,我们在测试代码中用到了它
它的用法与标准库 100% 兼容,并且性能有较大提升。我们一起粗略的看下是怎么做到的,如下:
reflect2
利用 modern-go/reflect2 减少运行时调度开销
...
type StructDescriptor struct {
Type reflect2.Type
Fields []*Binding
}
...
type Binding struct {
levels []int
Field reflect2.StructField
FromNames []string
ToNames []string
Encoder ValEncoder
Decoder ValDecoder
}
type Extension interface {
UpdateStructDescriptor(structDescriptor *StructDescriptor)
CreateMapKeyDecoder(typ reflect2.Type) ValDecoder
CreateMapKeyEncoder(typ reflect2.Type) ValEncoder
CreateDecoder(typ reflect2.Type) ValDecoder
CreateEncoder(typ reflect2.Type) ValEncoder
DecorateDecoder(typ reflect2.Type, decoder ValDecoder) ValDecoder
DecorateEncoder(typ reflect2.Type, encoder ValEncoder) ValEncoder
}
struct Encoder/Decoder Cache
类型为 struct 时,只需要反射一次 Name 和 Type,会缓存 struct Encoder 和 Decoder
var typeDecoders = map[string]ValDecoder{}
var fieldDecoders = map[string]ValDecoder{}
var typeEncoders = map[string]ValEncoder{}
var fieldEncoders = map[string]ValEncoder{}
var extensions = []Extension{}
....
fieldNames := calcFieldNames(field.Name(), tagParts[0], tag)
fieldCacheKey := fmt.Sprintf("%s/%s", typ.String(), field.Name())
decoder := fieldDecoders[fieldCacheKey]
if decoder == nil {
decoder = decoderOfType(ctx.append(field.Name()), field.Type())
}
encoder := fieldEncoders[fieldCacheKey]
if encoder == nil {
encoder = encoderOfType(ctx.append(field.Name()), field.Type())
}
文本解析优化
小结
相较于官方标准库,第三方库 json-iterator/go
在运行时上做的更好。这是它快的原因
有个需要注意的点,在 Go1.10 后 map
类型与标准库的已经没有太大的性能差异。但是,例如 struct
类型等仍然有较大的性能提高
总结
在本文中,我们首先进行了性能测试,再分析了不同方案,得知为什么了快慢的原因。那么最终在选择方案时,可以根据不同的应用场景去抉择:
- 对性能开销有较高要求:选用
for
,开销最小 - 中规中矩:选用
for range
,大对象慎用 - 量小、占用小、数量可控:选用
json.Marshal/Unmarshal
的方案也可以。其重复代码少,但开销最大
在绝大多数场景中,使用哪种并没有太大的影响。但作为工程师你应当清楚其利弊。以上就是不同的方案分析概要,希望对你有所帮助 :)
Go json Marshal & UnMarshal 的一点小 trick
在编写 Web Service 等涉及数据序列化和反序列化的场景,对于 JSON 类型的数据,在 Go 中我们经常会使用到 encoding/json Package。最近微有所感,小水一篇
omitempty
JSON 数据的 UnMarshal 我们经常会配合 Struct Tags 使用,让 Struct 的 Filed 与 JSON 数据的指定 property 绑定。
如果要序列化为 Go Struct 的 JSON 数据对应的 Fields 相关的 JSON properties 是缺失的,我们经常会用 omitempty 标记 Go Fields,序列化时,JSON 数据中缺少的属性将会被设置为 Go 中对应的 zero-value,比如:
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name string `json:"name"`
Age string `json:"age,omitempty"`
Weak bool `json:"weak,omitempty"`
}
func main() {
jsonData := `{"name":"ShanSan"}`
req := Person{}
_ = json.Unmarshal([]byte(jsonData), &req)
fmt.Printf("%+v", req)
fmt.Println(req.Age)
}
// output
// {Name:ShanSan Age: Weak:false}
//
Go Playground Link
但上面的例子对于一些场景的处理可能会有问题。看下下面这个例子:
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name string `json:"name"`
Age string `json:"age,omitempty"`
Weak bool `json:"weak,omitempty"`
}
func main() {
jsonData := `{"name":"ShanSan", "age": ""}`
req := Person{}
_ = json.Unmarshal([]byte(jsonData), &req)
fmt.Printf("%+v", req)
fmt.Println(req.Age)
}
// output
// {Name:ShanSan Age: Weak:false}
//
可以看到 age 为 ""
时,和缺省时的结果是一样的。很显然,上面的写法,缺省的字段和空字段是没有被区分开的。对于一些数据的 Update 操作,比如我们只想 Update Name 字段,对应的 JSON 数据为 {"name":"ShanSan"}
,执行上述的反序列化动作,Age 字段会被设置为 empty string,Waek 也被设置为了 false,这显然不是我们想看到的。
nil 一下
我们可以指针类型(pointer type)对上面的情况区分一下:
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name *string `json:"name"`
Age *string `json:"age,omitempty"`
Weak *bool `json:"weak,omitempty"`
}
func main() {
jsonData := `{"name":"ShanSan"}`
jsonDataEmptyAge := `{"name":"ShanSan", "age": ""}`
req := Person{}
reqEmptyAge := Person{}
_ = json.Unmarshal([]byte(jsonData), &req)
_ = json.Unmarshal([]byte(jsonDataEmptyAge), &reqEmptyAge)
fmt.Printf("%+v", req)
fmt.Printf("%+v", reqEmptyAge)
}
// {Name:0xc000010390 Age:<nil> Weak:<nil>}{Name:0xc0000103c0 Age:0xc0000103d0 Weak:<nil>}
emmm,缺省的字段为 nil 了。
Marshal 的时候
序列化 struct 的时候,如果使用了 omitempty,也会出现类似上面反序列化的情况,对于缺省的 field 或者 zero-value,序列化得到的 JSON 数据也会缺省相关属性,此时我们也可以通过 pointer 保留相关字段,如下:
package main
import (
"encoding/json"
"fmt"
)
type Student struct {
Name string `json:"name"`
Score int `json:"score,omitempty"`
}
type StudentWithPointer struct {
Name string `json:"name"`
Score *int `json:"score,omitempty"`
}
func main() {
student := Student{
Name: "ShanSan",
Score: 0,
}
score := 0
studentWithPointer := StudentWithPointer{
Name: "ShanSan",
Score: &score,
}
data, _ := json.Marshal(student)
dataWithPointer, _ := json.Marshal(studentWithPointer)
fmt.Println(string(data))
fmt.Println(string(dataWithPointer))
}
// {"name":"ShanSan"}
// {"name":"ShanSan","score":0}
Go Playground
参考
- encoding/json
- Differentiate between empty and not-set fields with JSON in Golang
- Go''s "omitempty" explained
本文由博客一文多发平台 OpenWrite 发布!
Go json 自定义 Unmarshal 避免判断 nil
腾讯《Go安全指南》中提到【必须】nil指针判断:进行指针操作时,必须判断该指针是否为nil,防止程序panic,尤其在进行结构体Unmarshal时。但如果每次使用都要判断一下是否 nil 防止 panic的话,那么这样的代码就会比较麻烦,这里我们可以使用一个自定义的方法,来避免这种情况。
使用默认的 Unmarshal 方法
package main
import (
"encoding/json"
"fmt"
)
type A struct {
Name string `json:"name"`
Num *int `json:"num"`
}
func main() {
var a A
err := json.Unmarshal([]byte(`{"name": "hsowan"}`), &a)
if err != nil {
panic(err)
}
fmt.Println(a.Name)
// 每次使用都要判断一下是否 nil 防止 panic
if a.Num != nil {
fmt.Println(*a.Num)
}
}
自定义的 Unmarshal 方法
当字段为 nil 时,在 Unmarshal 时进行初始化,这样就可以避免使用的时候发生 panic, 同时也不需要在使用的时候判断是否为 nil 了。
package main
import (
"encoding/json"
"fmt"
)
type A struct {
Name string `json:"name"`
Num *int `json:"num"`
}
func (a *A) UnmarshalJSON(b []byte) error {
type alias A
aux := (*alias)(a)
if err := json.Unmarshal(b, &aux); err != nil {
return err
}
if aux.Num == nil {
aux.Num = new(int)
}
return nil
}
func main() {
var a A
err := json.Unmarshal([]byte(`{"name": "hsowan"}`), &a)
if err != nil {
panic(err)
}
fmt.Println(a.Name)
fmt.Println(*a.Num)
}
参考
- [译]自定义Go Json的序列化方法
go json 解析 Marshal 和 Unmarshal
Decoder:
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"strings"
)
func main ( ) {
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 )
}
}
Marshal:
package main
import (
"encoding/json"
"fmt"
"os"
)
func main ( ) {
type ColorGroup struct {
ID int
Name string
Colors [ ] string
}
group := ColorGroup {
ID : 1 ,
Name : "Reds" ,
Colors : [ ] string { "Crimson" , "Red" , "Ruby" , "Maroon" } ,
}
b , err := json. Marshal ( group )
if err != nil {
fmt. Println ( "error:" , err )
}
os. Stdout . Write ( b )
}
RawMessage :
package main
import (
"encoding/json"
"fmt"
"log"
)
func main ( ) {
type Color struct {
Space string
Point json. RawMessage // delay parsing until we know the color space
}
type RGB struct {
R uint8
G uint8
B uint8
}
type YCbCr struct {
Y uint8
Cb int8
Cr int8
}
var j = [ ] byte ( ` [
{ "Space" : "YCbCr" , "Point" : { "Y" : 255 , "Cb" : 0 , "Cr" : - 10 } } ,
{ "Space" : "RGB" , "Point" : { "R" : 98 , "G" : 218 , "B" : 255 } }
] ` )
var colors [ ] Color
err := json. Unmarshal ( j , & colors )
if err != nil {
log . Fatalln ( "error:" , err )
}
for _ , c := range colors {
var dst interface { }
switch c. Space {
case "RGB" :
dst = new ( RGB )
case "YCbCr" :
dst = new ( YCbCr )
}
err := json. Unmarshal ( c. Point , dst )
if err != nil {
log . Fatalln ( "error:" , err )
}
fmt. Println ( c. Space , dst )
}
}
Unmarshal:
package main
import (
"encoding/json"
"fmt"
)
func main ( ) {
var jsonBlob = [ ] byte ( ` [
{ "Name" : "Platypus" , "Order" : "Monotremata" } ,
{ "Name" : "Quoll" , "Order" : "Dasyuromorphia" }
] ` )
type Animal struct {
Name string
Order string
}
var animals [ ] Animal
err := json. Unmarshal ( jsonBlob , & animals )
if err != nil {
fmt. Println ( "error:" , err )
}
fmt. Printf ( "%+v" , animals )
}
我们今天的关于在UnmarshalJSON函数中调用json.Unmarshal而不引起堆栈溢出的分享已经告一段落,感谢您的关注,如果您想了解更多关于for-loop 与 json.Unmarshal 性能分析概要、Go json Marshal & UnMarshal 的一点小 trick、Go json 自定义 Unmarshal 避免判断 nil、go json 解析 Marshal 和 Unmarshal的相关信息,请在本站查询。
本文标签: