• 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    这个例子结果输出如下:

    {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
    					}
    				}
                }   
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    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开发笔记

  • 相关阅读:
    【Web安全】HTML5安全
    MySQL(五) 数据恢复
    [论文评析]Single Image Haze Removal Using Dark Channel Prior
    【ROS2要素】xml、GDF、URDF的关系
    赋能工业物联网 | 数据驱动,加速智能制造
    python安全工具开发笔记(五)——python数据库编程
    机器比人更需要通证
    Windows平台上安装MySql 5.6 /8.0等的各种问题解决办法汇总
    Python二进制文件转换为文本文件
    【计算机视觉】目标跟踪任务概述和算法介绍
  • 原文地址:https://blog.csdn.net/xz_studying/article/details/126570040