go源码分析json的序列化与反序列化

json的数据类型

Gojson
boolbool
浮点数/整数常规数字
string(utf-8)string(Unicode)
array/slicearray
[]bytestring(Base64)
structJSON对象
map[string]TJSON对象

1、 marshal空接口:interface{}类型其实是个空接口,即没有方法的接口。go的每一种类型都实现了该接口。因此,任何其他类型的数据都可以赋值给interface{}类型。无论是string,int,bool,还是指针类型等,都可赋值给interface{}类型,在marshal序列化的时候且正常编码。

  type Stu struct {
      Name  interface{} `json:"name"`
      Age   interface{}
      HIgh  interface{}
      sex   interface{}
      Class interface{} `json:"class"`
  }
  type Class struct {
      Name  string
      Grade int
  }
  //实例化一个数据结构,用于生成json字符串
  stu := Stu{
      Name: "张三",
      Age:  18,
      HIgh: true,
      sex:  "男",
  }
  cla := new(Class)
  cla.Name = "1班"
  cla.Grade = 3
  stu.Class=cla
  //Marshal失败时err!=nil
  jsonStu, err := json.Marshal(stu)
  if err != nil {
      fmt.Println("生成json字符串错误")
  }
  
  //jsonStu是[]byte类型,转化成string类型便于查看
  fmt.Println(string(jsonStu))
  //输出 {"name":"张三","Age":18,"HIgh":true,"class":{"Name":"1班","Grade":3}}

2、 unMarshal空接口: 对于复合数据,在unmarshal的时候,如果接收体中配的项被声明为interface{}类型,go都会默认解析成map[string]interface{}类型。如果我们想直接解析到struct Class对象中,可以将接受体对应的项定义为该struct类型。

  type httpResp struct {
  	RetCode int      `json:"ret_code"`
  	Data interface{} `json:"data"`
  	Message string   `json:"message"`
  }
  
  //若response中的字段Data为复合数据,则response的data字段经过unMarshal后会变成map[string]interface{}的type形式。
  
  json.Unmarshal([]byte(response), &httpRsp)
  

json的序列化

1、Marshal方法 :用于将struct、map、slice序列化为json数据。

定义结构体的时候,只有字段名是大写的,才会被编码到json当中。

//将struct对象序列化成json,将map序列化成json将slice序列化成json
func Marshal(v interface{}) ([]byte, error) {
	e := newEncodeState()

	err := e.marshal(v, encOpts{escapeHTML: true})
	if err != nil {
		return nil, err
	}
	buf := append([]byte(nil), e.Bytes()...)

	e.Reset()
	encodeStatePool.Put(e)

	return buf, nil
}

标签: 每个结构字段的编码可以通过结构字段标签中“json”键下存储的格式字符串来定制。格式字符串给出字段的名称,可能后跟逗号分隔的选项列表。名称可能为空,以指定选项而不覆盖默认字段名称。

  1. “omitempty”选项指定如果字段具有空值,定义为false,0,零指针,nil接口值以及任何空数组,切片,映射或字符串,则该字段应从编码中省略。
    Addr string json:"addr,omitempty"

  2. 作为特殊情况,如果字段标签是“ - ”,则该字段总是被省略。一般用于数据库的ID字段。json: "-"

  3. golang是静态类型语言,对于类型定义的是不能动态修改。在json处理当中,struct tag的string可以起到部分动态类型的效果。有时候输出的json希望是数字的字符串,而定义的字段是数字类型,那么就可以使用string选项。

type Account struct {
    Email    string  `json:"-"`
    Password string  `json:"password,omitempty"`
    Money    float64 `json:"money,string"`
}

2、流式编码器方法 : 编码json文件

json.Marshal实际上只是对json.Encoder的封装,因此使用json.Encoder同样可以编码JSON。函数原型如下:

// Encode writes the JSON encoding of v to the stream,
// followed by a newline character.
//
func (enc *Encoder) Encode(v interface{}) error {}

调用示例:

func main(){
    b := &bytes.Buffer{}
    encoder := json.NewEncoder(b)
    err := encoder.Encode(结构体/map类型值)
    if err != nil{
	    panic(err)
    }
    fmt.Println(b.String())
}

json的反序列化

1、Unmarshal方法: 将json数据反序列化为其它数据结构
//将json反序列化成struct对象将json 反序列化到map中将json反序列化到slice中
func Unmarshal(data []byte, v interface{}) error {
	// Check for well-formedness.
	// Avoids filling out half a data structure
	// before discovering a JSON syntax error.
	var d decodeState
	err := checkValid(data, &d.scan)
	if err != nil {
		return err
	}

	d.init(data)
	return d.unmarshal(v)
}
2、流式解码器方法: 将json数据反序列化为其它数据结构

调用json的NewDecoder方法构造一个Decode对象,最后使用这个对象的Decode方法赋值给定义好的结构对象。对于字串,可是使用strings.NewReader方法,让字串变成一个Stream对象。对于json数组而言,json.Decoder会一个一个json元素进行加载,不会把整个json数组读到内存里面。

函数原型:

// func NewDecoder(r io.Reader) *Decoder
// func (dec *Decoder) Decode(v interface{}) error
err = json.NewDecoder(resp.Body).Decode(value)

代码示例:

var jsonString string = `{
    "username":"rsj217@gmail.com",
    "password":"123"
}`

func Decode(r io.Reader)(u *User, err error)  {
    u = new(User)
    err = json.NewDecoder(r).Decode(u)
    if err != nil{
        return
    }
    return
}

func main() {
    user, err := Decode(strings.NewReader(jsonString))
    if err !=nil{
        log.Fatalln(err)
    }
    fmt.Printf("%#v\n",user)
}

json.Decoder和json.Unmarshal的场景区别:

  1. Use json.Decoder if your data is coming from an io.Reader stream, or you need to decode multiple values from a stream of data. For the case of reading from an HTTP request, I’d pick json.Decoder since you’re obviously reading from a stream.

  2. Use json.Unmarshal if you already have the JSON data in memory.


json RawMessage 延迟解析

RawMessag是原始编码后的json值。含json.RawMessage字段的结构,在反序列化时会完整的接收json串对象的[]byte形式。延迟解析在实际使用时才解析它的具体类型。使用json.RawMessage方式,将json的字串继续以byte数组方式存在。

// 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.
type RawMessage []byte

// MarshalJSON returns m as the JSON encoding of m.
func (m RawMessage) MarshalJSON() ([]byte, error) {
	if m == nil {
		return []byte("null"), nil
	}
	return m, nil
}

// UnmarshalJSON sets *m to a copy of data.
func (m *RawMessage) UnmarshalJSON(data []byte) error {
	if m == nil {
		return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
	}
	*m = append((*m)[0:0], data...)
	return nil
}
代码分析
// test ...
type test struct {
	WorkLoadTemplate json.RawMessage `json:"workload_template"`
}

// test2 ...
type test2 struct {
	WorkLoadTemplate string  `json:"workload_template"`
}

testjson := 
`{
"workload_template":{"kind":"Deployment","apiVersion":"apps/v1","metadata":{"name":"tars.tarsnotify.test3","namespace":"center","creationTimestamp":null},"spec":{"replicas":1,"selector":null,"template":{"metadata":{"creationTimestamp":null},"spec":{"containers":[{"name":"application","image":"tars.dockerhub.com:5000/darren/tarsnotify:1.0.0","resources":{},"imagePullPolicy":"IfNotPresent"}]}},"strategy":{"type":"RollingUpdate","rollingUpdate":{"maxUnavailable":0,"maxSurge":1}}},"status":{}}
}`
  • testjson串 反序列化到test结构体会成功,json.RawMessage使得该字段保留原始的json串。

  • testjson串反序列化到test2结构体失败,因为在json串中workload_template对应的是一个json对象即go中的结构体,map等。


testjson2 := `{
				"workload_template":"{\"kind\":\"Deployment\",\"apiVersion\":\"apps/v1\",\"metadata\":{\"name\":\"tars.tarsnotify.test3\",\"namespace\":\"center\",\"creationTimestamp\":null},\"spec\":{\"replicas\":1,\"selector\":null,\"template\":{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"application\",\"image\":\"tars.dockerhub.com:5000/darren/tarsnotify:1.0.0\",\"resources\":{},\"imagePullPolicy\":\"IfNotPresent\"}]}},\"strategy\":{\"type\":\"RollingUpdate\",\"rollingUpdate\":{\"maxUnavailable\":0,\"maxSurge\":1}}},\"status\":{}}"
			}`
  • testjson串反序列到test2结构体成功。因为在json串中workload_template对应的是一个string对象即go中的string类型。


var d test
var d2 test2

testjson="1"

t.WorkLoadTemplate=[]byte(testjson)
t2.WorkLoadTemplate=testjson

fmt.Printf("d: %v", d)  // 字节的ascii码形式 {[49]}

fmt.Printf("d2: %v", d2) //结构体字符串  {1}

jsonstr, err = json.Marshal(d) 
fmt.Printf("d: %v", string(jsonstr))  //  {"workload_template":1}

jsonstr, err = json.Marshal(d2)
fmt.Printf("d2: %v", string(jsonstr)) // {"workload_template":"1"}

  • 基于同一个string串“1”的序列化结果不一致,因为在两个结构体中的workload_template数据类型不一致,不同类型的编码器不同,导致编码结果不同。

不定字段解析

对于未知json结构的解析,不同的数据类型可以映射到接口或者使用延迟解析。有时候,会遇到json的数据字段都不一样的情况。

1、interface{}方法。例如需要解析下面一个json字串:

var jsonString string = `{
        "things": [
            {
                "name": "Alice",
                "age": 37
            },
            {
                "city": "Ipoh",
                "country": "Malaysia"
            },
            {
                "name": "Bob",
                "age": 36
            },
            {
                "city": "Northampton",
                "country": "England"
            }
        ]
    }`

json字串的是一个对象,其中一个key things的值是一个数组,这个数组的每一个item都未必一样,大致是两种数据结构,可以抽象为person和place。即,定义下面的结构体:

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

type Place struct {
    City    string `json:"city"`
    Country string `json:"country"`
}

接下来我们Unmarshal json字串到一个map结构,然后迭代item并使用type断言的方式解析数据:

func decode(jsonStr []byte) (persons []Person, places []Place) {
    var data map[string][]map[string]interface{}
    err := json.Unmarshal(jsonStr, &data)
    if err != nil {
        fmt.Println(err)
        return
    }

    for i := range data["things"] {
        item := data["things"][i]
        if item["name"] != nil {
            persons = addPerson(persons, item)
        } else {
            places = addPlace(places, item)
        }

    }
    return
}

迭代的时候会判断item是否是person还是place,然后调用对应的解析方法:

func addPerson(persons []Person, item map[string]interface{}) []Person {
    name := item["name"].(string)
    age := item["age"].(float64)
    person := Person{name, int(age)}
    persons = append(persons, person)
    return persons
}

func addPlace(places []Place, item map[string]interface{})([]Place){
    city := item["city"].(string)
    country := item["country"].(string)
    place := Place{City:city, Country:country}
    places = append(places, place)
    return places
}

2、RawMessage方法

func addPerson(item json.RawMessage, persons []Person)([]Person){
    person := Person{}
    if err := json.Unmarshal(item, &person); err != nil{
        fmt.Println(err)
    }else{
        if person != *new(Person){
            persons = append(persons, person)
        }
    }
    return persons
}

func addPlace(item json.RawMessage, places []Place)([]Place){
    place :=Place{}
    if err := json.Unmarshal(item, &place); err != nil{
        fmt.Println(err)
    }else{
        if place != *new(Place){
            places = append(places, place)
        }
    }
    return places
}


func decode(jsonStr []byte)(persons []Person, places []Place){
    var data map[string][]json.RawMessage
    err := json.Unmarshal(jsonStr, &data)
    if err != nil{
        fmt.Println(err)
        return
    }

    for _, item := range data["things"]{
        persons = addPerson(item, persons)
        places = addPlace(item, places)
    }
    return
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值