JSON字符串的交集

I'm trying to find a way to use one JSON string as a "template" of sorts to apply to another JSON string. For instance, if my template looks as follows:

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

which I then apply to the following JSON string:

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

I'd like a resultant JSON string as follows:

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

Unfortunately I can't rely on either the template nor the input to be of a fixed format so I can't marshall/unmarshall into defined interfaces.

I got as far as writing a recursive function that traverses the template to construct a slice of string with the name of each node that is to be included.

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
}

I call this function as follows:

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

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

I was then going to write a second function that takes this slice of node names to construct a JSON string from the input JSON string but ran out of steam and started thinking that I might be on the wrong track anyway.

Any help on this would be appreciated.

Simply create a function which "clones" a map based on a template and a source map.

The solution would iterate over the entries of the template map, and for each (k, v) pair generate an entry in the destination map as follows:

  • If v is not a map, simply get the value for the k key from the source map, and use this in the destination.

  • If v is also a map, then call this "cloner" recursively with the new template map being v and the new source being the value from the source for the k key. The result of this recursive call will be the value for the k key in the destination map.

This is how it could look like:

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
}

And that's all.

Testing it:

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

Output with your example JSONs (try it on the Go Playground):

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

Notes:

The solution assumes the source map conforms to the template. That is, if the template contains a map for some key, the source map is also expected to contain a map for the same key. If this cannot be guaranteed, the procMap() function should be extended with a check to avoid a runtime panic, like this:

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

Also note that JSON arrays (slices) are not treated in any special way, meaning if the template contains a slice, the value from the source is used as-is, and no recursion happens if the slice contains maps. The solution can easily be extended to handle slices too, which is left as an exercise for the reader.