golang之JSON处理


JSON是常用的序列化格式之一,go中对其也有很好的支持。

JSON

golang中提供了encoding/json可方便地处理已知结构的json。

type Server struct {
	ServerName string
	ServerIP   string
}
type ServerAry struct {
	Servers []Server
}

func jsonTest() {
	var srv ServerAry
	str := `{"Servers":[{"ServerName":"SH-Net", "ServerIP":"127.0.0.1"},
		{"ServerName":"BJ-Net", "ServerIP":"127.0.0.2"}]}`
	json.Unmarshal([]byte(str), &srv)
	fmt.Println(srv)

	res, err := json.Marshal(srv)
	if err != nil {
		fmt.Println("Marshal json fail:", err)
	}
	fmt.Println(string(res))
}

Tag标签

tag是结构体的元信息,运行时通过反射机制读取;一般定义在相应字段的后面,格式为:

  • 标签由冒号分割:前面为类型,后面为标签名;
  • 一个字段可有多个标签,之间通过空格分割;
fieldName fieldType  `key1:"value1" key2:"value2"`

json中的键名可能与结构体中的字段名并不相同(特别是首字母大小写),这时就需要通过tag标签来设定:

type Product struct {
    Name      string  `json:"name"`
    ProductID int64   `json:"-"` // 表示不进行序列化
    Number    int     `json:"number,omitempty"`
    Price     float64 `json:"price"`
    IsOnSale  bool    `json:"is_on_sale,string"`
}
 
// 序列化后
// {"name":"honor6","number":1000,"price":1499,"is_on_sale":"false"}

json的tag格式如下:

Key type  json:"name,opts..."`
  • 字段名(Key)必须首字母大写,否则会被忽略(不序列化);
  • 不指定tag(或tag中没有标签名,如json:",string")则使用字段名做键名;
  • 标签名为-,标识忽略(不序列化);
  • opts选项(多个时,中间用逗号分割):
    • omitempty:对应字段为零值时,不序列化;
    • string:对应字段序列化为字符串(仅适用于字符串、浮点、整数或布尔类型);

与map转换

JSON字符串可转换为map[string]interface{},值实际对应类型为:

  • 普通类型为:bool、float64(整数与小数都映射为浮点数)与string;
  • 数组为:接口数组[]interface{}
  • 对象为:接口mapmap[string]interface{}
func JsonTest() {
	str := `{
				"Servers": [
					{
						"ServerName": "SH-Net",
						"ServerIP": "127.0.0.1"
					},
					{
						"ServerName": "BJ-Net",
						"ServerIP": "127.0.0.2"
					}
				],
				"Note": "Test",
				"OK": true,
				"Count": 123,
				"Score": 0.1
			}
		`

	var mpJson map[string]interface{}
	json.Unmarshal([]byte(str), &mpJson)
	for k, v := range mpJson {
		showKV(v, k, "")
	}
}


func showKV(v interface{}, k string, prefix string) {
	fmt.Print(prefix, k)
	switch v.(type) {
	case []interface{}:
		sub := v.([]interface{})
		fmt.Print("[")
		for _, sv := range sub {
			showKV(sv, "", prefix)
		}
		fmt.Println("]")
	case map[string]interface{}:
		sub := v.(map[string]interface{})
		fmt.Println()
		for sk, sv := range sub {
			showKV(sv, sk, prefix+"\t")
		}
	case float64, string, bool:
		fmt.Printf("=%v(%T)\n", v, v)
	default:
		fmt.Println("=", v)
	}
}

// Note=Test(string)                 
// OK=true(bool)                     
// Count=123(float64)                
// Score=0.1(float64)                
// Servers[                          
//         ServerName=SH-Net(string) 
//         ServerIP=127.0.0.1(string)
//                                   
//         ServerName=BJ-Net(string) 
//         ServerIP=127.0.0.2(string)
// ]             

自定义序列化MarshalJSON

有时,标准序列化方法不能满足需求,这是可自定义序列化与反序列化接口来控制序列化过程:

定义结构体方法
可以定义一个时间的结构体,定义好序列化与反序列化后,其他地方直接使用即可:

const tmFormat = "2006-01-02 15:04:05"

type JsonTime struct {
	time.Time
}

func (t *JsonTime) MarshalJSON() ([]byte, error) {
	var stamp = fmt.Sprintf("\"%s\"", t.Time.Format(tmFormat))
	return []byte(stamp), nil
}
func (t *JsonTime) UnmarshalJSON(b []byte) error {
	b = bytes.Trim(b, "\"")
	ext, err := time.Parse(tmFormat, string(b))
	if err != nil {
		fmt.Println("ERROR:", err)
	}
	*t = JsonTime{ext}
	return nil
}

type ExtTimeExample struct {
	Time  JsonTime `json:"time"`
	Name  string   `json:"name"`
	Score float64  `json:"score"`
}

func testExtJson() {
	ext := &ExtTimeExample{JsonTime{time.Now()}, "Mike", 0.12345678}
	js, _ := json.Marshal(ext)
	fmt.Printf("JSON: %v\n", string(js))
	tmp := &ExtTimeExample{}
	json.Unmarshal(js, tmp)
	fmt.Printf("Unmarshal: %+v\n", tmp)
}

匿名覆盖方法
在序列化时,通过同名字段覆盖‘父类’中同名字段的方式实现的。

const tmFormat = "2006-01-02 15:04:05"

type JsonExample struct {
	Name       string    `json:"name"`
	Score      float64   `json:"score"`
	RecordTime time.Time `json:"recordtime"`
}

const tmFormat = "2006-01-02 15:04:05"

func (s *JsonExample) MarshalJSON() ([]byte, error) {
	type TmpExample JsonExample
	return json.Marshal(struct {
		RecordTime string `json:"recordtime"`
		//Score      string `json:"score"`
		*TmpExample
	}{
		RecordTime: s.RecordTime.Format(tmFormat),
		//Score:      fmt.Sprintf("%.3f", s.Score),
		TmpExample: (*TmpExample)(s),
	})
}

func (s *JsonExample) UnmarshalJSON(data []byte) error {
	type TmpExample JsonExample

	ss := struct {
		RecordTime string `json:"recordtime"`
		*TmpExample
	}{
		TmpExample: (*TmpExample)(s),
	}

	if err := json.Unmarshal(data, &ss); err != nil {
		fmt.Println(err)
		return err
	}

	var err error
	s.RecordTime, err = time.Parse(tmFormat, ss.RecordTime)
	if err != nil {
		return err
	}

	return nil
}

func testSelfMarshal() {
	exa := &JsonExample{
		Name:       "Mike",
		Score:      0.12345678,
		RecordTime: time.Now(),
	}

	js, _ := json.Marshal(exa)
	fmt.Println("JSON:", string(js))

	tmp := &JsonExample{}
	json.Unmarshal(js, tmp)
	fmt.Printf("Unmarshal: %+v", tmp)
}

go-simplejson

golang提供的json虽然简单易用,但不够灵活;在结构未知的情况下了借助"github.com/bitly/go-simplejson" 包(https://pkg.go.dev/github.com/bitly/go-simplejson#Json)

反序列化

simplejson提供了多种反序列化方法,可从文件或字符串中生成JSON:

  • NewJson(body []byte) (*Json, error)
  • NewFromReader(r io.Reader) (*Json, error)
  • (j *Json) UnmarshalJSON(p []byte) error

从文件中读取内容后,反序列化:

func ReadJson(fileName string) *simpleJson.Json {
	contents, err := ioutil.ReadFile(fileName)
	if err != nil {
		log.Fatalln("read json file failed:", err)
		return nil
	}

	js, err := simpleJson.NewJson(contents)
	if err != nil {
		log.Fatalln("Parse json fail:", err)
		return nil
	}
	return js
}

序列化

通过Set/SetPath可设定键值(或修改),通过Del可删除键值;

修改或新生成的simpleJson.Json,可通过Encode/EncodePretty/MarshalJSON序列化成Json字符串([]byte类型)。

获取值

simpleJson.Json内部是K-V格式的:

  • Get/GetPath:根据键值获取(返回*Json),即使对应键不存在也不会返回nil(需要通过jData.Interface()是否为nil判断);
  • GetIndex(i):根据索引(需要是array类型)获取值(返回*Json);
  • Del:删除指定键值;
  • Set/SetPath:设定指定键值;

取值

Get获取到的是simpleJson.Json,可:

  • (j *Json) Interface() interface{}:获取接口;
  • (j *Json) Int() (int, error):转换为Int(还可float、string、bool等);
  • (j *Json) MustFloat64(defValue) float64:转换为浮点(还可int、string、bool等),不存在时,返回defValue。

通过Map可把simpleJson.Json转换为map[string]interface{}

all, _ := jData.Map()
fmt.Println("### Elements of ", tag, " size: ", len(all))
for k, v := range all {
	fmt.Printf("\t%v=%v (%T)\n", k, v, v)
}

接口转值

Json值是interface{}(数字(整数与浮点数)都为json.Number类型的,字符串为string类型的),使用前需要转换为实际的类型:

func ParseJsonFloat(jNum interface{}) float64 {
	switch v := jNum.(type) {
	case json.Number:
		f, _ := jNum.(json.Number).Float64()
		return f
	case string:
		str, _ := jNum.(string)
		f, _ := strconv.ParseFloat(str, 64)
		return f
	default:
		log.Printf("*ERROR: Invalid number type: %v(%v)", jNum, v)
		return 0
	}
}

读写示例

以如下格式的文件为例:

{
	"Score": 0.564812,
	"box": {
		"x": 10,
		"y": 10,
		"width": 20,
		"height": 20
	},
	"success": true,
	"roll": "0",
	"direct": 1,
	"ObjectId": 10,
	"cameraId": "1"
}

从文件读取后:

  • Map:获取键值对;
  • Get/GetPath:获取指定值;
  • Del:删除指定值;
  • Set/SetPath:设定值;
func simpleJsonTest(fName string) {
	reader, err := os.Open(fName)
	if err != nil {
		fmt.Println("Open file fail: ", err)
		return
	}
	defer reader.Close()

	jData, err := simpleJson.NewFromReader(reader)
	if err != nil {
		fmt.Println("Reader json fail: ", err)
		return
	}

	mpData, _ := jData.Map()
	fmt.Println("Elements of JSON:")
	for k, v := range mpData {
		fmt.Printf("\t%v=%v(%T)\n", k, v, v)
	}

	width := jData.GetPath("box", "width")
	if width != nil {
		fmt.Println("Width: ", width.MustInt(0))
	}
	fmt.Println("Height: ", jData.GetPath([]string{"box", "height"}...))
	
	success, _ := jData.Get("success").Bool()
	fmt.Println("Success: ", success)

	jData.Del("success")
	jData.SetPath([]string{"box", "width"}, 1)
	jData.Set("roll", 1)
	out, _ := jData.EncodePretty()
	fmt.Println(string(out))
}

// Elements of JSON:
//         direct=1(json.Number)                                         
//         ObjectId=10(json.Number)                                      
//         cameraId=1(string)                                            
//         Score=0.564812(json.Number)                                   
//         box=map[height:20 width:20 x:10 y:10](map[string]interface {})
//         success=true(bool)                                            
//         roll=0(string)                                                
// Width:  20    
// Height:  &{20}                                                         
// Success:  true                                                        
// {                                                                     
//   "ObjectId": 10,                                                     
//   "Score": 0.564812,                                                  
//   "box": {                                                            
//     "height": 20,                                                     
//     "width": 1,                                                       
//     "x": 10,                                                          
//     "y": 10                                                           
//   },                                                                  
//   "cameraId": "1",                                                    
//   "direct": 1,                                                        
//   "roll": 1                                                           
// }              
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值