Nicholas Pappas
Nicholas Pappas

Reputation: 10624

Using JSON Merge Patch, how do I reference members of sub-collection?

Using the JSON Merge Patch specification, is there a way to identify members of a sub-collection in the target - for modification or deletion?

With the twist, that key may be flagged as readOnly!

For example, given the following original JSON document:

{
  "a": "b",
  "c": [
    {
      "key": "d",
      "prop1": "e",
      "prop2": "f"
    },
    {
      "key": "g",
      "prop": "h"
    }
  ]
}

My gut (often wrong) tells me to send the following request:

PATCH /target HTTP/1.1
  "c": [
    {
      "key": "d",
      "prop1": "z"
    },
  ]

To get the result:

{
  "a": "b",
  "c": [
    {
      "key": "d",
      "prop1": "z",
      "prop2": "f"
    }
  ]
}

Does this, so far, make sense according to the spec?

But, what if key is flagged as readOnly in an OpenAPI schema? In that case we shouldn't technically be allowed to pass the key (a UUID) on the wire.

Upvotes: 2

Views: 3834

Answers (1)

wp78de
wp78de

Reputation: 18980

It looks like this is not how it works. I have tested your scenario using json-merge patch, an implementation of the JSON Merge Patch RFC 7396,

var jsonmergepatch = require('json-merge-patch');    
var source = {
  "a": "b",
  "c": [
    {
      "key": "d",
      "prop1": "e",
      "prop2": "f"
    },
    {
      "key": "g",
      "prop": "h"
    }
  ]
};    
var patch = {
  "c": [
    {
      "key": "d",
      "prop1": "z"
    },
  ]
};
var target = jsonmergepatch.apply(source, patch);
console.log(JSON.stringify(target));

and this outputs:

{
    "a": "b",
    "c": [{
            "key": "d",
            "prop1": "z"
        }
    ]
}

My explanation is, the patch value specified for c is not an object but an array, and therefore the existing value is replaced entirely. When looking at RFC 7396 (which obsoletes RFC 7386) we find the application of a merge patch in pseudo-code:

define MergePatch(Target, Patch):
 if Patch is an Object:
   if Target is not an Object:
     Target = {} # Ignore the contents and set it to an empty Object
   for each Name/Value pair in Patch:
     if Value is null:
       if Name exists in Target:
         remove the Name/Value pair from Target
     else:
       Target[Name] = MergePatch(Target[Name], Value)
   return Target
 else:
   return Patch

My conclusion here is, an array has no name-value pair, and therefore does not enter the recursive processing but is simply returned, which means gets replaced by the patch value in your case.

Regarding the readOnly flag, I strongly believe the merge patch operation is not schema-aware. The RFC does not mention anything in this respect.

Upvotes: 3

Related Questions