|
|
// 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
|
|
|
}
|