golang之JSON处理

admin 2023-07-10 PM 1008℃ 0条
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
// }
标签: none

非特殊说明,本博所有文章均为博主原创。

评论啦~