Go json tag的大小写匹配问题

前言

通常struct的json tag要求与要解析json数据对应的key完全一致,如果只是大小写不一致会发生什么呢?

以一个例子来说明问题:

package main

import (
	"encoding/json"
	"fmt"
)

type Example struct {
	ID   int    `json:"id"`
	Name string `json:"NAME"`
}

type Example2 struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Name2 string `json:"NAME"`
}

type Example3 struct {
	ID    int    `json:"id"`
	Name2 string `json:"NAME"`
	Name  string `json:"name"`
}

func main() {
	var example Example
	json.Unmarshal([]byte(`{"id":1,"NAMe":"n2","name":"n1"}`), &example)
	fmt.Println(example)
	var example2 Example2
	json.Unmarshal([]byte(`{"id":1,"NAMe":"n2","name":"n1"}`), &example2)
	fmt.Println(example2)
	var example3 Example3
	json.Unmarshal([]byte(`{"id":1,"NAMe":"n2","name":"n1"}`), &example3)
	fmt.Println(example3)

}

这个例子结果输出如下:

{1 n1}

{1 n1 }

{1 n2 n1}

为何会出现这样的结果?

我们先从源码找答案。

更多内容分享,欢迎关注公众号:Go开发笔记

源码解析

json unmarshal关于object解析位于decode.go文件中的object func.

object关于struct filed的解析

type field struct {
	name      string
	nameBytes []byte                 // []byte(name)
	equalFold func(s, t []byte) bool // bytes.EqualFold or equivalent

    ...
}

func (d *decodeState) object(v reflect.Value) error {
    ...
            var f *field
			if i, ok := fields.nameIndex[string(key)]; ok {
				// Found an exact name match.
				f = &fields.list[i]
			} else {
				// Fall back to the expensive case-insensitive
				// linear search.
				for i := range fields.list {
					ff := &fields.list[i]
					if ff.equalFold(ff.nameBytes, key) {
						f = ff
						break
					}
				}
            }   
    ...
}

fields.nameIndex类型是map[string]int,存储了struct filed的tag及对应的index,解析json时,会先查找json中的key是否存在于fields.nameIndex中,如果存在则获取filed并设置其值;如果不存在,则会依照struct filed的顺序依次比较json中的key,然后获取filed并赋值。

关于EqualFold的比较

equalFold调用的是bytes.EqualFold

// EqualFold reports whether s and t, interpreted as UTF-8 strings,
// are equal under Unicode case-folding, which is a more general
// form of case-insensitivity.
func EqualFold(s, t []byte) bool {
	for len(s) != 0 && len(t) != 0 {
		// Extract first rune from each.
		var sr, tr rune
		if s[0] < utf8.RuneSelf {
			sr, s = rune(s[0]), s[1:]
		} else {
			r, size := utf8.DecodeRune(s)
			sr, s = r, s[size:]
		}
		if t[0] < utf8.RuneSelf {
			tr, t = rune(t[0]), t[1:]
		} else {
			r, size := utf8.DecodeRune(t)
			tr, t = r, t[size:]
		}

		// If they match, keep going; if not, return false.

		// Easy case.
		if tr == sr {
			continue
		}

		// Make sr < tr to simplify what follows.
		if tr < sr {
			tr, sr = sr, tr
		}
		// Fast check for ASCII.
		if tr < utf8.RuneSelf {
			// ASCII only, sr/tr must be upper/lower case
			if 'A' <= sr && sr <= 'Z' && tr == sr+'a'-'A' {
				continue
			}
			return false
		}

		// General case. SimpleFold(x) returns the next equivalent rune > x
		// or wraps around to smaller values.
		r := unicode.SimpleFold(sr)
		for r != sr && r < tr {
			r = unicode.SimpleFold(r)
		}
		if r == tr {
			continue
		}
		return false
	}

	// One string is empty. Are both?
	return len(s) == len(t)
}

EqualFold的比较原理是:
(1)依次比较两个[]byte的对应位置上的每个rune,
(2)先直接比较rune,相同则继续,不相同则判断是否忽略大小写时是否一致,相同则继续,否则返回false。

即EqualFold在比较时会忽略大小写。

所以当json中的key不存在于fields.nameIndex中时,会依照struct filed的顺序忽略大小写依次进行比较,如果此时一致,则会获取对应的filed,然后赋值。

总结

整体来说,json解析时,会按照如下规则顺序处理:

  1. 判断key中的tag是否存在,存在则获取对应的filed并赋值
  2. 按照filed顺序依次判断是否匹配(忽略大小写),只要匹配则获取对应的filed然后不再后续匹配(即只能匹配到第一个符合条件的filed),后赋值
  3. 按照以上规则均不匹配,则无法解析至struct中

案例回答

按照以上规则,可以回答文章开头的结果:

  1. example:
  • id存在于struct的filed中,filed为ID,可以直接解析为1。
  • NAMe不存在于struct的filed中,但忽略大小写时与NAME filed匹配,可以解析为n2。
  • name不存在于struct的filed中,但忽略大小写时与NAME filed匹配,可以解析为n1。

因此解析结果为{1 n1}

  1. example2
  • id存在于struct的filed中,filed为ID,可以直接解析为1。
  • NAMe不存在于struct的filed中,但忽略大小写时与Name filed匹配,可以解析为n1。
  • name存在于struct的filed中,可以直接解析为n1。

没有key解析至Name2,Name2值为空字符串,因此解析结果为{1 n1 }

  1. example3
  • id存在于struct的filed中,filed为ID,可以直接解析为1。
  • NAMe不存在于struct的filed中,但忽略大小写时与Name2 filed匹配,可以解析为n2。
  • name存在于struct的filed中,filed为Name,可以直接解析为n1。

因此解析结果为{1 n2 n1}

至此,你对json tag解析时的匹配规则了解了吗?

更多内容分享,欢迎关注公众号:Go开发笔记

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值