关于golang:golang的json过滤器随心所欲的构造自己的json数据随意复用结构体

35次阅读

共计 10400 个字符,预计需要花费 26 分钟才能阅读完成。

反对过滤的数据结构

github 地址:https://github.com/liu-cn/jso… json-filter
构造体 / 嵌套构造体 / 匿名构造体 / 指针
构造体切片 / 数组
map

应用场景

有时候你可能会遇到这种状况:一个构造体想要在不同的接口下返回不同的 json 数据字段。
举个例子:
一个模仿用户的 model,实在环境中的字段会要比这个多得多。

type User struct {
    UID      uint   `json:"uid"`    
    Avatar   string `json:"avatar"`
    Nickname string `json:"nickname"`

    Sex        int       `json:"sex"`
    VipEndTime time.Time `json:"vip_end_time"`
    Price      string    `json:"price"`
    
    // 上面可能还有更多的字段,实在的环境中可能有几十个字段。}

设计的起因

文章接口中须要返回用户 User 的数据,然而你只须要在文章接口中返回用户的 UID Avatar 和 Nickname 这三个字段就够了,并不想暴露出其余用不上的字段,因为返回更多的字段意味着更大的数据,编解码更加耗时,而且也会耗费更多的网络带宽,传输速度会变慢。

这时候你可能须要创立一个新的构造体而后把这三个字段赋值,序列化并返回,
而后业务越来越多,用到 User 的中央越来越多,profile 接口下须要返回用户的 Nickname VipEndTime 和 Price 这三个字段,这时候比可能又要再创立一个构造体,再复制字段,赋值,序列化返回,就像这样

传统的解决方案

    user:=User{}
------------------------------ 形式 1 ------------------------
// 定义一个新的 model
    type UserArticleRes struct {
        UID      uint   `json:"uid"`
        Avatar   string `json:"avatar"`
        Nickname string `json:"nickname"`
    }
    userRes :=UserArticleRes{
        UID: user.UID,
        Avatar: user.Avatar,
        Nickname: user.Nickname,
    }
    
    return userRes

------------------------------ 形式 2 ------------------------
// 或者间接应用匿名构造体。res:= struct {
            UID      uint   `json:"uid"`
            Avatar   string `json:"avatar"`
            Nickname string `json:"nickname"`
        }{
            UID:      user.UID,
            Avatar:   user.Avatar,
            Nickname: user.Nickname,
        }
    return res

方正这两种形式都大差不差,而后更多的场景都用到了 User 构造体,私信接口,直播接口,评论等等,而后可能须要反复以上的操作,如果这样搞的话会有很多 model 须要治理,而且开发效率很低,一个字段改掉其余很多构造体都会受到影响,治理起来麻烦,不灵便,不易扩大,

这时候可能须要用到 go 的 json 过滤器来得心应手的复用这一个构造体,得心应手的返回你想要的 json 数据结构,这时能够用 json-filter 这个 go 的开源我的项目

json-filter 地址:https://github.com/liu-cn/jso…

json-filter 疾速体验

依照下面的需要试着用 json-filter 来解决一下看看是怎么解决的。

select 选择器

指定字段须要用到的场景

package main

import (
    "fmt"
    "github.com/liu-cn/json-filter/filter"
    "time"
)

type User struct {UID uint `json:"uid,select(article)"`
    // 在 json 的字段名前面写上 select(场景 0 | 场景 1),括号中能够写多个场景用 | 宰割

    Avatar string `json:"avatar,select(article)"`
    //Avatar 标记了 article 的示意 article 接口 (场景) 会用到这个字段。Nickname string `json:"nickname,select(article|profile)"`
    //Nickname 这里示意 article 和 profile 接口 (场景) 都会用到该字段

    Sex int `json:"sex"`
    // 这个没标记用 json-filter 过滤时不会解析该字段。VipEndTime time.Time `json:"vip_end_time,select(profile)"`
    // 这个标记了 profile 示意只会在 profile 接口编码应用

    Price string `json:"price,select(profile)"`
    // 同上
}

// NewUser 初始化一个用户构造体,把所有字段都赋值。func NewUser() User {
    return User{
        UID:        0,
        Avatar:     "https://gimg2.baidu.com",
        Nickname:   "昵称",
        Sex:        1,
        VipEndTime: time.Now().Add(time.Hour * 24 * 365),
        Price:      "19999.9",
    }
}

func main() {

    // 用起来比较简单
    articleUser := filter.SelectMarshal("article", NewUser()) // 返回值是一个 filter.Filter 的构造体

    //filter.Filter 罕用的就三个办法

    //articleUser.MustJSON()
        // 返回过滤后的 json 字符串,外部曾经帮忙 json.Marshal 过了
        // 如果过程中出错间接 panic,Must 前缀的办法都是如此。(倡议测试时用这个,不便测试)

    //articleUser.JSON()
        // 返回过滤后的 json 字符串和可能产生的谬误

    //articleUser.Interface()
        // 返回过滤后待 json.Marshal 的数据结构,此时还未编码成 json 能够把此数据间接挂载在待 json 解析的中央进行解析。// 此时咱们能够间接用 MustJSON()打印解析后的 json
    fmt.Println(articleUser.MustJSON())
    {"avatar":"https://gimg2.baidu.com","nickname":"昵称","uid":0}
    // 能够看到曾经间接编码成 json 了,并且只编码了 User 构造体 tag 中 select 标记中抉择到的字段。// 如果咱们想要编码 profile 接口下的 json 数据的话很简略,// 因为咱们曾经在构造体 tag 里把须要的字段曾经标注过了。// 此时间接换个场景,而后间接打印
    profileUser := filter.SelectMarshal("profile", NewUser())
    fmt.Println(profileUser.MustJSON())
    {"nickname":"昵称","price":"19999.9","vip_end_time":"2023-03-08T14:10:15.439302+08:00"}
    // 能够看到输入的 json 是咱们须要的 json 数据结构,那些不想要的字段都被过滤掉了。}

如果咱们又减少了新的应用场景,那么咱们只须要在 User 的构造体标签里增加新的场景就行了不须要反复的创立构造体了,例如咱们新加的私信接口里用到用户,须要 Nickname Avatar 和 Sex 这三个字段的话,看一下怎么扩大。
还得在原来的 User 构造体上进行操作。

    type User struct {UID uint `json:"uid,select(article)"`
        Avatar string `json:"avatar,select(article|chat)"` // 这里增加 chat
        Nickname string `json:"nickname,select(article|profile|chat)"`// 这里增加 chat
        Sex int `json:"sex,select(chat)"` // 这个也增加 chat
        VipEndTime time.Time `json:"vip_end_time,select(profile)"`
        Price string `json:"price,select(profile)"`
    }

    
    chatUser := filter.SelectMarshal("chat", NewUser())
    fmt.Println(chatUser.MustJSON())
    {"avatar":"https://gimg2.baidu.com","nickname":"昵称","sex":1}
    // 很不便就打印出了你想要的构造,如果你还想在这个场景增加一个返回的字段,那再再别的字段也加上这个场景就能够了,很不便。

而后你发现一个问题,就是 Nickname 这个场景你每个场景都要用到,select()里曾经逐步变得收缩,每个场景都写显得太啰嗦,你不想每增加一个场景就把新的场景也加进去,你心愿有个通用标识符能示意此字段无论任何场景我都要用,
那么很简略,我提供了 $any 标识符用用来标识此字段任何场景都会用到,此时你能够把 Nickname 字段的标签简化一下由 select(article|profile|chat)换成 select($any)
咱们能够试一下成果

    type User struct {UID uint `json:"uid,select(article)"`
        Avatar string `json:"avatar,select(article|chat)"`
        Nickname string `json:"nickname,select($any)"`// 这里换成 $any 示意所有场景都用
        Sex int `json:"sex,select(chat)"`
        VipEndTime time.Time `json:"vip_end_time,select(profile)"`
        Price string `json:"price,select(profile)"`
    }

    chatUser := filter.SelectMarshal("chat", NewUser())
    fmt.Println(chatUser.MustJSON())
    {"avatar":"https://gimg2.baidu.com","nickname":"昵称","sex":1}
    // 能够看到仍旧输入了你想要的后果。

这时候又有新的应用场景了,你又发现问题了,这次是一个评论的场景,这次除了 Price 字段之外其余的所有字段都须要用到,你再去增加新的场景时会发现,好麻烦居然须要把 Price 字段外的 5 个字段全副都打上标记,如果构造体字段过多的确会很好受,很不优雅,于是又有了新的选择器

omit 选择器

指定场景排除该字段字段,用法和 select 刚好相同,咱们能够疾速体验一下,并解决下面评论接口的需要

type User struct {UID uint `json:"uid,select(article)"`
    Avatar string `json:"avatar,select(article|chat)"`
    Nickname string `json:"nickname,select($any)"`
    Sex int `json:"sex,select(chat)"`
    VipEndTime time.Time `json:"vip_end_time,select(profile)"`
    
    Price string `json:"price,select(profile),omit(comment)"` // 在这里增加 omit(comment)选择器间用, 宰割。// 此时示意 Price 在 comment 时会被疏忽掉,不会被 json 解析,这时就省的应用 select 来标记 5 个字段了。}

    // 此时须要调用的办法也不同了用 select 选择器解析须要用 SelectMarshal 办法,用 omit 选择器须要用 OmitMarshal 办法解析,commentUser := filter.OmitMarshal("comment", NewUser())
    fmt.Println(commentUser.MustJSON())
    {"avatar":"https://gimg2.baidu.com","nickname":"昵称","price":"19999.9","sex":1,"uid":0,"vip_end_time":"2023-03-08T14:47:09.390381+08:00"}
    // 能够看到是本人想要的后果
    //OmitMarshal 用法大同小异,仍旧能够排除多个字段,仍旧能够用 $any 示意任意场景都排除该字段,// 比方 password 字段任何场景都不违心裸露就能够用 omit($any)来示意任何场景都疏忽解析该字段
    // 能够参考 SelectMarshal 的办法来应用 OmitMarshal

留神一个问题:

** 事实应用中更多是用 articleUser.Interface() 这个办法,因为 MustJSON()间接编码为 json 如果再进行返回序列化的时候会产生反复编码,把 json 当作字符串编码,能够间接看一个 gin 的例子。


// 封装一下返回的构造
func OkWithData(data interface{}, c *gin.Context) {
    // 这里会在进行 json 编码
    c.JSON(200, Response{
        Code: 0,
        Msg:  "ok",
        Data: data, // 这个 data 应该是一个构造体 / 切片或者 map 不应该是曾经解析好的 json 字符串
    })
}

// 解决办法
func GetUserFilter(c *gin.Context) {OkWithData(filter.SelectMarshal("article", NewUser()).MustJSON(), c)
}

看一下这样的处理结果是怎么的?{
    "Code": 0,
    "Msg": "ok",
    "Data": "{"nickname":"boyan","uid":1}"
}
Data 被当字符串编码了,所以想要正确的,被其余的办法编码就须要用 Interface()这个办法,他会返回一个过滤后待编码的 map,你能够了解就是一个 struct,struct 是能够被再次编码的,能够看一下成果。

很简略换个办法就能够了
func GetUserFilter(c *gin.Context) {OkWithData(filter.SelectMarshal("article", NewUser()).Interface(), c) // 换成这样
}

输入后果,能够看到解析的没故障。{
    "Code": 0,
    "Msg": "ok",
    "Data": {
        "nickname": "boyan",
        "uid": 1
    }
}

json-filter 的高级过滤

对数组和切片的间接过滤

func main() {
    type Tag struct {ID   uint   `json:"id,select(all)"`
        Name string `json:"name,select(justName|all)"`
        Icon string `json:"icon,select(chat|profile|all)"`
    }

    tags := []Tag{   // 切片和数组都反对
        {
            ID:   1,
            Name: "c",
            Icon: "icon-c",
        },
        {
            ID:   1,
            Name: "c++",
            Icon: "icon-c++",
        },
        {
            ID:   1,
            Name: "go",
            Icon: "icon-go",
        },
    }

    fmt.Println(filter.SelectMarshal("justName", tags).MustJSON())
    //---> 输入后果:[{"name":"c"},{"name":"c++"},{"name":"go"}]

    fmt.Println(filter.SelectMarshal("all", tags).MustJSON())
    //---> 输入后果:[{"icon":"icon-c","id":1,"name":"c"},{"icon":"icon-c++","id":1,"name":"c++"},{"icon":"icon-go","id":1,"name":"go"}]

    fmt.Println(filter.SelectMarshal("chat", tags).MustJSON())
    //---> 输入后果:[{"icon":"icon-c"},{"icon":"icon-c++"},{"icon":"icon-go"}]

}

你认为 json-filter 就这两把刷子?只能过滤这么简略的数据结构吗?
其实不是,只有官网 json.Marshal()能解析的,json-filter 都能够间接过滤。

咱们来看一个简单的场景 这个 Profile 构造体能够说是相当简单了,咱们只想疏忽带 name 的字段,Lang 构造体嵌套在 Profile 的第二层,Art 嵌套到第三层,想要层层都过滤一些字段,如果用传统的形式,创立新构造体的话有点让人头皮发麻,此时 json-filter 仍旧能够在任何简单数据结构帮你间接过滤数据,咱们只增加了两个标记,能够体验一下后果。

type Profile struct {
    UID     uint   `json:"uid"`
    LangAge []Lang `json:"lang_age"`}
    
type Lang struct {Name string `json:"name,omit(omitName)"` // 疏忽该字段
    Arts []*Art `json:"arts"`}
    
type Art struct {Name    string                 `json:"name,omit(omitName)"` // 疏忽该字段
    Profile map[string]interface{} `json:"profile"`
    Values  []string               `json:"values"`}

func NewProfile() Profile {
    return Profile{
        UID: 0,
        LangAge: []Lang{
            {
                Name: "c++",   // 这个字段不须要,Arts: []*Art{
                    {
                        Name: "c++",// 这个字段也不须要,Profile: map[string]interface{}{"c++": "cpp",},
                        Values: []string{"cpp1", "cpp2"},
                    },
                },
            },
            {
                Name: "Go",// 这个字段不须要,Arts: []*Art{
                    {
                        Name: "Golang",// 这个字段也不须要,Profile: map[string]interface{}{"Golang": "go",},
                        Values: []string{"Golang", "Golang1"},
                    },
                },
            },
        },
    }
}

omitName := filter.OmitMarshal("omitName", NewProfile())
fmt.Println(omitName.MustJSON())
// 输入后果
{"LangAge":[{"Arts":[{"Profile":{"c++":"cpp"},"Values":["cpp1","cpp2"]}]},{"Arts":[{"Profile":{"Golang":"go"},"Values":["Golang","Golang1"]}]}],"UID":0}

丑化一下,能够看到 name 字段全副被过滤掉了,即便嵌套在深处能够正确解析。

{
    "lang_age":[
        {
            "arts":[
                {
                    "profile":{"c++":"cpp"},
                    "values":[
                        "cpp1",
                        "cpp2"
                    ]
                }
            ]
        },
        {
            "arts":[
                {
                    "profile":{"Golang":"go"},
                    "values":[
                        "Golang",
                        "Golang1"
                    ]
                }
            ]
        }
    ],
    "uid":0
}

再用 select 换个场景试一下,这次我只想抉择 name 字段,其余的都不要,试一下。
这次你须要这么写,因为 Lang 和 Art 构造体都是 Profile 构造体上嵌套的构造体,所以想要 Lang 构造体的数据就必须标记 LangAge 为选中状态,同样想要 Art 中 Name 字段 Arts 也要被选中,不然这些父节点间接疏忽掉,他的所有字段 (子节点) 就不会被解析了,实质上解析时会编码成 n 叉树结构,所以要想要子节点数据就必须保障父节点被选中,间接看 tag 就能看懂。

type Profile struct {
    UID     uint   `json:"uid"`
    LangAge []Lang `json:"lang_age,select(justName)"`
}

type Lang struct {Name string `json:"name,omit(omitName),select(justName)"`
    Arts []*Art `json:"arts,select(justName)"`
}

type Art struct {Name    string                 `json:"name,omit(omitName),select(justName)"` // 疏忽该字段
    Profile map[string]interface{} `json:"profile"`
    Values  []string               `json:"values"`}


    justName := filter.SelectMarshal("justName", NewProfile())
    fmt.Println(justName.MustJSON())
    {"lang_age":[{"arts":[{"name":"c++"}],"name":"c++"},{"arts":[{"name":"Golang"}],"name":"Go"}]}

json 丑化后成果,能够看到这后果正式想要的后果。

{
    "lang_age":[
        {
            "arts":[
                {"name":"c++"}
            ],
            "name":"c++"
        },
        {
            "arts":[
                {"name":"Golang"}
            ],
            "name":"Go"
        }
    ]
}

对于匿名构造体的过滤

type Page struct {PageInfo int `json:"pageInfo,select($any)"`
    PageNum  int `json:"pageNum,select($any)"`
}

type Article struct {Title  string `json:"title,select(article)"`
    Page   `json:",select(article)"`     // 这种 tag 字段名为空的形式会间接把该构造体开展,当作匿名构造体解决  
  //Page `json:"page,select(article)"` // 留神这里 tag 里标注了匿名构造体的字段名,所以解析时会解析成对象,不会开展 
    Author string `json:"author,select(admin)"`
}

func main() {
    article := Article{
        Title: "c++ 从研发到脱发",
        Page: Page{
            PageInfo: 999,
            PageNum:  1,
        },
    }

    articleJson := filter.SelectMarshal("article", article)
    fmt.Println(articleJson)
  // 输入后果 --->  {"pageInfo":999,"pageNum":1,"title":"c++ 从研发到脱发"}
}

如果不想开展 Page 构造体想变成具名构造体的话很简略

// 不想把 Page 构造体开展也是能够的,很简略,把匿名构造体 Page 的构造体标签名加上
// 接下来把
Page `json:",select(article)"` 换成
Page   `json:"page,select(article)"`

// 接下来看一下输入成果,能够看到字段没有被开展
{"page":{"pageInfo":999,"pageNum":1},"title":"c++ 从研发到脱发"}

对于 map 也是能够间接过滤的,就不演示了,能够本人试一下,写的太多了就没人违心看了,

举荐应用的姿态


type User struct {UID    uint        `json:"uid,select($any)"` // 标记了 $any 无论抉择任何场景都会解析该参数
    Name   string      `json:"name,select(article|profile|chat)"`
    Avatar interface{} `json:"data,select(profile|chat)"`
}

func (u User) ArticleResp() interface{} {
    // 这样当你前面想要优化性能时能够在这里进行优化,return filter.SelectMarshal("article",u).Interface()}

func (u User) ProfileResp() interface{} {
    // 这样当你前面想要优化性能时能够在这里进行优化,return filter.SelectMarshal("profile",u).Interface()}

func (u User) ChatResp() interface{} {
    // 如果性能呈现瓶颈,想要优化
    chat:= struct {
        UID    uint    `json:"uid"`
        Name   string  `json:"name"`
    }{
        UID: u.UID,
        Name: u.Name,
    }
    return chat
}

正文完
 0