一尘不染

JSON字符串的交集

go

我正在尝试找到一种方法来将一个JSON字符串用作各种“模板”以应用于另一个JSON字符串。例如,如果我的模板如下所示:

{
   "id": "1",
   "options": {
      "leatherseats": "1",
      "sunroof": "1"
   }
}

然后将其应用于以下JSON字符串:

{
   "id": "831",
   "serial": "19226715",
   "options": {
      "leatherseats": "black",
      "sunroof": "full",
      "fluxcapacitor": "yes"
   }
}

我想要如下所示的结果JSON字符串:

{
   "id": "831",
   "options": {
      "leatherseats": "black",
      "sunroof": "full",
   }
}

不幸的是,我既不能依赖模板也不可以是固定格式的输入,因此我无法编组/解组到已定义的接口中。

我已经编写了一个遍历模板的递归函数,以构造一个带有每个要包含的节点名称的字符串切片。

func traverseJSON(key string, value interface{}) []string {
    var retval []string
    unboxed, ok := value.(map[string]interface{})
    if ok {
        for newkey, newvalue := range unboxed {
            retval = append(retval, recurse(fmt.Sprintf("%s.%s", key, newkey), newvalue)...)
        }
    } else {
        retval = append(retval, fmt.Sprintf("%s", key))
    }
    return retval
}

我称这个函数如下:

template := `my JSON template here`
var result map[string]interface{}
json.Unmarshal([]byte(template), &result)

var nodenames []string
nodenames = append(nodenames, traverseJSON("", result)...)

然后,我要编写第二个函数,该函数使用这部分节点名称从输入的JSON字符串构造JSON字符串,但是用尽了精力,开始思考我可能还是走错了路。

任何帮助,将不胜感激。


阅读 208

收藏
2020-07-02

共1个答案

一尘不染

只需创建一个基于模板和源地图“克隆”地图的函数。

该解决方案将遍历模板映射的条目,并为每一(k, v)对在目标映射中生成一个条目,如下所示:

  • 如果v不是地图,只需k从源地图获取键的值,然后在目标位置使用它。

  • 如果v也是一个映射,则递归调用此“克隆”,新模板映射为v,新源为k密钥源中的值。该递归调用的结果将是k目标映射中键的值。

它看起来像这样:

func procMap(tmpl, src map[string]interface{}) (dst map[string]interface{}) {
    dst = map[string]interface{}{}

    for k, v := range tmpl {
        if innerMap, ok := v.(map[string]interface{}); ok {
            dst[k] = procMap(innerMap, src[k].(map[string]interface{}))
        } else {
            dst[k] = src[k]
        }
    }

    return dst
}

就这样。

测试它:

// tmpljson is the template JSON
var tmpl map[string]interface{}
if err := json.Unmarshal([]byte(tmpljson), &tmpl); err != nil {
    panic(err)
}

// srcjson is the source JSON
var src map[string]interface{}
if err := json.Unmarshal([]byte(srcjson), &src); err != nil {
    panic(err)
}

dst := procMap(tmpl, src)

enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", "  ")
if err := enc.Encode(dst); err != nil {
    panic(err)
}

输出示例JSON(在Go Playground上尝试):

{
  "id": "831",
  "options": {
    "leatherseats": "black",
    "sunroof": "full"
  }
}

笔记:

该解决方案 假定
源映射符合模板。也就是说,如果模板包含某个键的映射,则源映射也应包含相同键的映射。如果不能保证,procMap()则应通过检查扩展该函数,以避免运行时出现恐慌,如下所示:

for k, v := range tmpl {
    if innerMap, ok := v.(map[string]interface{}); ok {
        if src2, ok2 := src[k].(map[string]interface{}); ok2 {
            dst[k] = procMap(innerMap, src2)
        } else {
            log.Printf("src is not conform to template at key %q", k)
        }
    } else {
        dst[k] = src[k]
    }
}

还要注意,JSON数组(切片)不会以任何特殊方式处理,这意味着如果模板包含切片,则按原样使用源中的值,并且如果切片包含地图,则不会进行递归。该解决方案也可以轻松扩展为处理切片,这留给读者练习。

2020-07-02