Re: [Json] JSON merge alternatives

Mark Nottingham <mnot@mnot.net> Wed, 19 March 2014 23:57 UTC

Return-Path: <mnot@mnot.net>
X-Original-To: json@ietfa.amsl.com
Delivered-To: json@ietfa.amsl.com
Received: from localhost (ietfa.amsl.com [127.0.0.1]) by ietfa.amsl.com (Postfix) with ESMTP id CB7151A0817 for <json@ietfa.amsl.com>; Wed, 19 Mar 2014 16:57:15 -0700 (PDT)
X-Virus-Scanned: amavisd-new at amsl.com
X-Spam-Flag: NO
X-Spam-Score: -1.902
X-Spam-Level:
X-Spam-Status: No, score=-1.902 tagged_above=-999 required=5 tests=[BAYES_00=-1.9, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001] autolearn=ham
Received: from mail.ietf.org ([4.31.198.44]) by localhost (ietfa.amsl.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id YKg72hoRil76 for <json@ietfa.amsl.com>; Wed, 19 Mar 2014 16:57:12 -0700 (PDT)
Received: from mxout-07.mxes.net (mxout-07.mxes.net [216.86.168.182]) by ietfa.amsl.com (Postfix) with ESMTP id 48BC71A0811 for <json@ietf.org>; Wed, 19 Mar 2014 16:57:12 -0700 (PDT)
Received: from [192.168.1.55] (unknown [118.209.54.174]) (using TLSv1 with cipher AES128-SHA (128/128 bits)) (No client certificate requested) by smtp.mxes.net (Postfix) with ESMTPSA id AB72B22E200; Wed, 19 Mar 2014 19:56:55 -0400 (EDT)
Content-Type: text/plain; charset="us-ascii"
Mime-Version: 1.0 (Mac OS X Mail 7.2 \(1874\))
From: Mark Nottingham <mnot@mnot.net>
In-Reply-To: <20140319234549.GA3471@localhost>
Date: Thu, 20 Mar 2014 10:56:52 +1100
Content-Transfer-Encoding: quoted-printable
Message-Id: <8489D083-3871-4516-8FD3-32749645DBB4@mnot.net>
References: <20140319234549.GA3471@localhost>
To: Nico Williams <nico@cryptonector.com>
X-Mailer: Apple Mail (2.1874)
Archived-At: http://mailarchive.ietf.org/arch/msg/json/kO3XZJieNt12Zqe3sd1VzjorgiM
Cc: json@ietf.org
Subject: Re: [Json] JSON merge alternatives
X-BeenThere: json@ietf.org
X-Mailman-Version: 2.1.15
Precedence: list
List-Id: "JavaScript Object Notation \(JSON\) WG mailing list" <json.ietf.org>
List-Unsubscribe: <https://www.ietf.org/mailman/options/json>, <mailto:json-request@ietf.org?subject=unsubscribe>
List-Archive: <http://www.ietf.org/mail-archive/web/json/>
List-Post: <mailto:json@ietf.org>
List-Help: <mailto:json-request@ietf.org?subject=help>
List-Subscribe: <https://www.ietf.org/mailman/listinfo/json>, <mailto:json-request@ietf.org?subject=subscribe>
X-List-Received-Date: Wed, 19 Mar 2014 23:57:16 -0000

Hi Nico,

  http://mailarchive.ietf.org/arch/msg/apps-discuss/mvUVvUt41tFTrPSOCfv7sfblTUM

Cheers,


On 20 Mar 2014, at 10:45 am, Nico Williams <nico@cryptonector.com> wrote:

> First, IMO the WG should in fact adopt a WG item (or more) for MIME
> types for use with PATCH to patch JSON texts (or entities with JSON
> representations).  This is commonly known as "JSON merge", IIUC.
> 
> I work with one Rails application that has an ad-hoc JSON merge like
> schema for representing updates (PUTs) to resources -- this schema is
> very specific to the application in question, therefore not reusable.
> 
> I can imagine a Ruby gem that handles a JSON merge PATCH in a completely
> general and reusable way.  And not just Ruby, of course.
> 
> Second, I believe the JSON merge proposals I've seen to date are no
> good.
> 
> The goal of any Internet JSON merge schema should be to:
> 
>   Concisely and generally express "edits" to a JSON text, efficiently
>   allowing:
> 
>    - replacement of any values
>    - prepend/insert/replace/append to arrays
>    - addition/removal of names from objects
>    - setting null values to object names
> 
>   no matter how deeply (subject to a max) or shallowly nested these
>   values be in the original, while being easy to produce and apply.
> 
> Note: it helps to use If-Match to make sure that the resource being
> PATCHed has not been modified since it was fetched.  This allows one to
> express edits with confidence.
> 
> I have two proposals: one based on augmenting a subset of the original
> JSON text, the other based a sequence of paths and edit instructions for
> the values found at those paths.
> 
> 
> Proposal #1: Patch as augmented subset of the original
> 
> Follow these steps to produce a patch:
> 
> - Start with the resource's JSON representation.
> 
> - Remove all interior object name/value pairs that are not on the way
>   to any value to be added/replaced/removed/otherwise edited.
> 
> - Remove all interior array elements that are not on the way to any
>   value to be added/replaced/removed UNLESS the element is in an array
>   to be edited (more on this below).
> 
> - Insert before each remaining array element its index.
> 
> - To replace any value with a scalar, just write the new value where
>   the old one appeared.
> 
> - To append to any array, just append -1 and the new value to it (after
>   the above removals and additions).
> 
> - For any array you want to do more to than append -> wrap the array in
>   an object with any of these named values:
> 
>    o "delete":  [<list of array indices>]
>    o "prepend": [<list of values to append>]
>    o "insert":  [<list of [<index>, <value>]>]
>    o "set":     [<list of [<index>, <value>]>]
>    o "new":     [<list of new elements to replace the old one's with>]
>    o "edits":   [<list of edits (see below)>]
> 
>   To disambiguate these objects from objects that could legitimately
>   appear in the resource the following named value MUST also be
>   present:
> 
>    o "<URN TBD>": true
> 
>   Replacements to be performed first, then deletions, then insertions,
>   then prepends, then "edits".  Edits are what would have appeared
>   had there been no need to wrap the array in order to do delete,
>   prepend, replace, or insert elements.
> 
> - To add name/value pairs to objects, just add them.
> 
> - If you want to delete values from objects -> wrap the object in an
>   object with any of these names:
> 
>    o "delete": [<list of name strings>]
>    o "edits":  <object containint edits>
> 
>   Edits are what would have appeared had there been no need to wrap the
>   object to delete name/value pairs.
> 
>   To disambiguate these names the following named value MUST also be
>   present:
> 
>    o "<URN TBD>": true
> 
> Values to be added/set appear as they should appear in the resource as
> patched.
> 
> Patch application is straightforward, obvious.
> 
> Example resource:
> 
>  {
>    "a": [ { "b": 1, "c": 2 }, { "d": { "e": [ 2, {} ] } } ],
>    "z": [ true, false, null, null, "hello" ]
>  }
> 
> Example edits:
> 
> - Delete that empty object (path a[0].d.e[1] in jq-speak)
> 
>   { "a": [ 1, { "d": { "e": { "delete": [ 1 ],
>                               "urn:ietf.org:TBD": true } } } ] }
> 
> - Replace the array [ 2, {} ] (path a[0].d.e in jq-speak) with the
>   value 0:
> 
>   { "a": [ 1, { "d": { "e": 0 } } ] }
> 
> - Append true to the array [ 2, {} ] (path a[0].d.e in jq-speak):
> 
>   { "a": [ 1, { "d": { "e": [ true ] } } ] }
> 
> - Delete the 1, prepend "foo", append "bar" to that same array:
> 
>   { "a": [ 1, { "d": { "e": { "delete": [ 0 ],
>                               "prepend": [ "foo" ],
>                               "edits": [ "bar" ],
>                               "urn:ietf.org:TBD": true } } } ] }
> 
> - Add a sibling name to "b" and "c":
> 
>   { "a": [ 0, { "f": "hello" } ] }
> 
> - Delete "b":
> 
>   { "a": [ 0, { "delete": [ "b" ], "urn:ietf.org:TBD": true } ] }
> 
> - Replace the "z" array with [1, 2, 3]:
> 
>   { "z": { "new": [ 1, 2, 3, ], "urn:ietf.org:TBD": true } }
> 
> Proposal #2: Use sequence of [<path>, <edit instruction>]
> 
> - Write the path to each value to be edited as an array of path
>   elements (strings for object names, numbers for array elements).
> 
> - To replace any value with a scalar value (non-array, non-object),
>   just write the path to the value and the new value as the
>   instruction.
> 
> - To add a value to an object just write the path to the object, append
>   the new value's name to the path, and the instruction will be the new
>   value.
> 
> - To add (append) a value to an array, write the path to the array,
>   append a -1 to the path, and the instruction will be the new value.
> 
> - Edit instructions for arrays (other than appending to, or replacing
>   the whole array with a scalar value) are any objects like:
> 
>    o "delete":  [<list of indices>] or true (if path names an array
>                                        element)
> 
>    o "add":     [<list of <value>s to append]
> 
>    o "insert":  [<list of [<index>, <value>] pairs>]
>    o "set":     [<list of [<index>, <value>] pairs>]
>    o "prepend": [<list of <value>s to prepend]
>    o "add":     [<list of <value>s to append]
>    o "new":     [<new list of values to replace old ones>]
> 
>   No magic URN name is needed in this case.
> 
> - To add an object
> 
> - Edit instructions for objects (other than replacement with a scalar
>   value) are objects with name/value pairs like:
> 
>    o "delete": [<list of name strings>]
>    o "merge":  [<object whose named value pairs will replace the
>                 corresponding ones of the object being edited>]
> 
>   No magic URN name is needed in this case.
> 
> The totality of the patch, then, is an array of
> [ <path>, <instruction> ] elements.
> 
> Any given path could be referenced multiple times in one patch.
> 
> Proposal #2 is pretty self-explanatory.  Examples left as an exercise
> for the reader.
> 
> Nico
> -- 
> 
> _______________________________________________
> json mailing list
> json@ietf.org
> https://www.ietf.org/mailman/listinfo/json

--
Mark Nottingham   http://www.mnot.net/