JSON
Tag标签
与map转换
自定义序列化MarshalJSON
go-simplejson
反序列化
序列化
获取值
取值
接口转值
读写示例
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{}
;
对象为:接口map map[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
// }