You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
naza/pkg/nazajson/json.go

157 lines
4.5 KiB
Go

// Copyright 2019, Chef. All rights reserved.
// https://github.com/q191201771/naza
//
// Use of this source code is governed by a MIT-style license
// that can be found in the License file.
//
// Author: Chef (191201771@qq.com)
package nazajson
import (
"encoding/json"
"errors"
"reflect"
"strings"
)
var ErrJson = errors.New("nazajson: fxxk")
type Json struct {
//raw []byte
m map[string]interface{}
}
func New(raw []byte) (Json, error) {
var j Json
err := j.Init(raw)
return j, err
}
func (j *Json) Init(raw []byte) error {
return json.Unmarshal(raw, &j.m)
}
// 判断 json 中某个字段是否存在
// @param path 支持多级格式,用句号`.`分隔,比如 log.level
func (j *Json) Exist(path string) bool {
return exist(j.m, path)
}
// ---------------------------------------------------------------------------------------------------------------------
// CollectNotExistFields
//
// @param data json字符串
// @param v 对应的结构体变量或者结构体指针变量。注意并不要求是反序列化赋值后的结构体变量内部主要是获取字段、tag等信息
// @param ignorePrefix 可选参数,可填入不需要收集的字段的前缀。
// 注意,这里只做简单字符串匹配,比如填入`a`,那么所有以`a`开头的全部会过滤掉(不光是`a.[xxx]`,还包含`ab.``ac.`等等)
// 以上语义以后可能会发送变化,建议使用方在字段名字相似的情况下,使用完成的字段名称
//
// @return 返回所有不存在的json字段组成的数组
//
func CollectNotExistFields(data []byte, v interface{}, ignorePrefix ...string) ([]string, error) {
j, err := New(data)
if err != nil {
return nil, err
}
typ := reflect.TypeOf(v)
ret, err := collectNotExistFields(j, "", typ, 0)
if err != nil || len(ignorePrefix) == 0 {
return ret, err
}
var filterRet []string
for _, notExistField := range ret {
shouldIgnore := false
for _, ignorePrefixItem := range ignorePrefix {
if strings.HasPrefix(notExistField, ignorePrefixItem) {
shouldIgnore = true
break
}
}
if !shouldIgnore {
filterRet = append(filterRet, notExistField)
}
}
return filterRet, nil
}
// ---------------------------------------------------------------------------------------------------------------------
// @param prefix 判断json是否存在的路径前缀如果没有设置为""
// 注意,路径可以是多级,但是最后的字符不需要`.`
// @param debugDepth 递归调用层级,调试时使用
//
func collectNotExistFields(j Json, prefix string, typ reflect.Type, debugDepth int) (notExists []string, err error) {
//nazalog.Debugf("[%d] > collectNotExistFields. typ=%+v", debugDepth, typ)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
if typ.Kind() != reflect.Struct {
return nil, ErrJson
}
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
//nazalog.Debugf("[%d] iterate field=%+v", debugDepth, field)
if field.Anonymous {
anonyNotExists, err := collectNotExistFields(j, prefix, field.Type, debugDepth+1)
if err != nil {
return anonyNotExists, err
}
notExists = append(notExists, anonyNotExists...)
}
// json的字段名
jk, ok := field.Tag.Lookup("json")
if !ok {
// 字段没有json tag直接忽略
continue
}
if prefix != "" {
jk = prefix + "." + jk
}
ok = j.Exist(jk)
if !ok {
// 注意,如果这一层的字段是结构体类型,且不存在,那么该结构体的所有字段都将不存在
// 此时有多种收集方式
// 我们的做法是选择不收集结构体自身,而是收集该结构体的所有字段
if field.Type.Kind() != reflect.Struct {
notExists = append(notExists, jk)
}
}
//nazalog.Debugf("[%d] check exist result. ok=%+v, prefix=%s, jk=%s", debugDepth, ok, prefix, jk)
if field.Type.Kind() == reflect.Struct {
subNotExists, err := collectNotExistFields(j, jk, field.Type, debugDepth+1)
if err != nil {
return subNotExists, err
}
notExists = append(notExists, subNotExists...)
}
}
//nazalog.Debugf("[%d] end. collect=%+v", debugDepth, notExists)
return
}
// 判断`m`的`path`是否存在
func exist(m map[string]interface{}, path string) bool {
ps := strings.Split(path, ".")
if len(ps) > 1 {
v, ok := m[ps[0]]
if !ok {
return false
}
mm, ok := v.(map[string]interface{})
if !ok {
return false
}
return exist(mm, ps[1])
}
_, ok := m[ps[0]]
return ok
}