schibsted / jslt

JSON query and transformation language
Apache License 2.0
638 stars 120 forks source link

Add smart merge of objects #39

Open ecerulm opened 6 years ago

ecerulm commented 6 years ago

Given two objects

{
 "nested": {
    "a": 1,
    "b": 2
  }
}
{
 "nested": {
    "b": "overwrite",
    "c": 3,
  }
}

The merge($a, $b) should produce

{
 "nested": {
    "a": 1,
    "b": "overwrite",
    "c": 3,
  }
}

Note that this is different from the current $a + $b in that + will blindly overwrite all keys producing

{
 "nested": {
    "b": "overwrite",
    "c": 3,
  }
}

where the whole nested key is replaced and not merged

ecerulm commented 6 years ago

@larsga pointed that this could be implemented as

def keys(obj)
  [for ($obj) .key]

def unique(values)
  let obj = {for ($values) . : .}
  keys($obj)

def merge(obj1, obj2)
  if (not($obj1))
    $obj2
  else if (not($obj2))
    $obj1
  else if (is-object($obj1) and is-object($obj2)) {
    for (unique(keys($obj1) + keys($obj2)))
      . : merge(get-key($obj1, .), get-key($obj2, .))
  } else if (is-array($obj1) and is-array($obj2))
    $obj1 + $obj2
  else
    error("Can't merge " + $obj1 + " and " + $obj2)

IMHO, it would be beneficial to have this a convenience built-in function in JSLT directly

larsga commented 5 years ago

I'm tempted to add this to the experimental module so we can get some experience with it in practice before nailing the exact behaviour. I'll see if I can get that done.

ecerulm commented 5 years ago

I didn't know there was an experimental module

ecerulm commented 5 years ago
def keys(obj)
  [for ($obj) .key]

def unique(values)
  let obj = {for ($values) . : .}
  keys($obj)

def merge(obj1, obj2)
  if ($obj1 == null) //  remember that $obj1 can be a boolean with value false so (not($obj1)) does not mean non existing
    $obj2
  else if ($obj2 == null )// same as above
    $obj1
  else if (is-object($obj1) and is-object($obj2)) {
    for (unique(keys($obj1) + keys($obj2)))
      . : merge(get-key($obj1, .), get-key($obj2, .))
  } else if (is-array($obj1) and is-array($obj2))
    $obj1 + $obj2
  else
    $obj2 // THIS IS DIFFERENT THAN THE @larsga PROPOSAL

let a = {
 "nested": {
    "a": 1,
    "b": 2
  }
}
let b = {
 "nested": {
    "b": "overwrite",
    "c": 3
  }
}

merge($a,$b)

produces the output (which is what I wanted):

{
  "nested" : {
    "a" : 1,
    "b" : "overwrite",
    "c" : 3
  }
}

Still I think this is probably good to have in the base JSTL, for me this is the behaviour I expected from the + operator.