Internet DRAFT - draft-snell-json-test

draft-snell-json-test







Individual Submission                                           J. Snell
Internet-Draft
Intended status: Informational                        September 24, 2013
Expires: March 28, 2014


                            JSON Predicates
                        draft-snell-json-test-07

Abstract

   JSON Predicates defines a syntax for serializing various predicate
   expressions as JSON Objects.

Status of This Memo

   This Internet-Draft is submitted to IETF in full conformance with the
   provisions of BCP 78 and BCP 79.

   Internet-Drafts are working documents of the Internet Engineering
   Task Force (IETF).  Note that other groups may also distribute
   working documents as Internet-Drafts.  The list of current Internet-
   Drafts is at http://datatracker.ietf.org/drafts/current/.

   Internet-Drafts are draft documents valid for a maximum of six months
   and may be updated, replaced, or obsoleted by other documents at any
   time.  It is inappropriate to use Internet-Drafts as reference
   material or to cite them other than as "work in progress."

   This Internet-Draft will expire on March 28, 2014.

Copyright Notice

   Copyright (c) 2013 IETF Trust and the persons identified as the
   document authors.  All rights reserved.

   This document is subject to BCP 78 and the IETF Trust's Legal
   Provisions Relating to IETF Documents
   (http://trustee.ietf.org/license-info) in effect on the date of
   publication of this document.  Please review these documents
   carefully, as they describe your rights and restrictions with respect
   to this document.









Snell                    Expires March 28, 2014                 [Page 1]

Internet-Draft               JSON Predicates              September 2013


Table of Contents

   1.  Introduction  . . . . . . . . . . . . . . . . . . . . . . . .   2
   2.  Predicate Objects . . . . . . . . . . . . . . . . . . . . . .   3
     2.1.  Predicate Context . . . . . . . . . . . . . . . . . . . .   5
     2.2.  First Order Predicates  . . . . . . . . . . . . . . . . .   5
       2.2.1.  "contains" and "contains-"  . . . . . . . . . . . . .   5
       2.2.2.  defined . . . . . . . . . . . . . . . . . . . . . . .   6
       2.2.3.  ends  . . . . . . . . . . . . . . . . . . . . . . . .   7
       2.2.4.  in  . . . . . . . . . . . . . . . . . . . . . . . . .   7
       2.2.5.  less  . . . . . . . . . . . . . . . . . . . . . . . .   8
       2.2.6.  matches . . . . . . . . . . . . . . . . . . . . . . .   9
       2.2.7.  more  . . . . . . . . . . . . . . . . . . . . . . . .  10
       2.2.8.  starts  . . . . . . . . . . . . . . . . . . . . . . .  10
       2.2.9.  test  . . . . . . . . . . . . . . . . . . . . . . . .  11
       2.2.10. type  . . . . . . . . . . . . . . . . . . . . . . . .  12
       2.2.11. undefined . . . . . . . . . . . . . . . . . . . . . .  14
     2.3.  Second-Order Predicates . . . . . . . . . . . . . . . . .  14
       2.3.1.  and . . . . . . . . . . . . . . . . . . . . . . . . .  15
       2.3.2.  not . . . . . . . . . . . . . . . . . . . . . . . . .  17
       2.3.3.  or  . . . . . . . . . . . . . . . . . . . . . . . . .  18
       2.3.4.  Nesting Second Order Predicates . . . . . . . . . . .  19
     2.4.  Error Handling  . . . . . . . . . . . . . . . . . . . . .  20
     2.5.  Using JSON Predicates within JSON Patch Documents . . . .  21
       2.5.1.  Conditional Patch Operations  . . . . . . . . . . . .  22
   3.  IANA Considerations . . . . . . . . . . . . . . . . . . . . .  24
   4.  Security Considerations . . . . . . . . . . . . . . . . . . .  25
   5.  References  . . . . . . . . . . . . . . . . . . . . . . . . .  25
     5.1.  Normative References  . . . . . . . . . . . . . . . . . .  25
     5.2.  Informative References  . . . . . . . . . . . . . . . . .  26
   Appendix A.  Non-Normative Example Implementation (Ruby)  . . . .  26
   Author's Address  . . . . . . . . . . . . . . . . . . . . . . . .  36

1.  Introduction

   This specification defines JSON Predicates, a JSON-based [RFC4627]
   syntax for the description and serialization of logical boolean
   predicate operations intended to be used in conjunction with other
   JSON-based mechanisms, such as JSON Patch [RFC6902], as a means of
   incorporating conditional processing.

   JSON Predicates can be used, for instance, to extend a JSON Patch
   [RFC6902] document to provide for a broader range of conditional
   processing options not currently supported by JSON Patch.

   Example: Given a source JSON document





Snell                    Expires March 28, 2014                 [Page 2]

Internet-Draft               JSON Predicates              September 2013


   {
     "a": {
       "b": {
         "c": "ABC!XYZ"
       }
     }
   }


   The following JSON Patch with JSON Predicates document will first
   test that the value of the "c" property is a string containing the
   character sequence "ABC" prior to applying the specified "replace"
   operation.

   [
     {
       "op": "and",
       "path": "/a/b/c",
       "apply": [
         {
           "op": "type",
           "value": "string"
         },
         {
           "op": "contains",
           "value": "ABC"
         }
       ]
     },
     {
       "op": "replace",
       "path": "/a/b/c",
       "value": 123
     }
   ]


   It is important to note this specification does not define a distinct
   JSON Predicates Document format.  Rather, it is the intent for JSON
   Predicates to be used within other JSON-based document formats.

   In this document, the key words "MUST", "MUST NOT", "REQUIRED",
   "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY",
   and "OPTIONAL" are to be interpreted as described in [RFC2119].

2.  Predicate Objects





Snell                    Expires March 28, 2014                 [Page 3]

Internet-Draft               JSON Predicates              September 2013


   A JSON Predicate is a JSON Object whose members describe a testable
   condition that evaluates as either true or false.

   The essential components of a JSON Predicate include:

   o  A label identifying the predicate operation,

   o  A reference to the value being tested, and

   o  The condition against which the referenced value is to be
      evaluated.

   Predicate objects MUST have exactly one "op" member whose value
   indicates the type of predicate operation to perform.  It's value
   MUST be one of: "and", "contains", "contains-", "defined", "ends",
   "ends-", "in", "in-", "less", "matches", "matches-", "more", "not",
   "or", "starts", "starts-", "test", "test-", "type", or "undefined".
   The semantics for each are defined in the sections that follow.

   Note that the value of the "op" member is case-sensitive and that
   each of the operations listed are in lower-case.  The value "Starts",
   for example, is not equivalent to "starts".

   If the "op" member specifies any value other than one of those listed
   above, the evaluation of the predicate operation MUST cease and be
   handled as if a boolean value of "false" was returned.  The
   application processing the predicate operation MAY signal that an
   error condition has occurred depending on the specific requirements
   of the application within which JSON Predicates are being used.

   The remaining structure of each predicate operation depends on the
   specific type.  There are two basic types of predicates.

   o  First Order Predicates that are used to test one name value pair
      against a single condition, and

   o  Second Order Predicates that aggregate one or more subordinate
      First or Second Order Predicates.

   In addition to the required "op" member, First Order Predicates have
   exactly one "path" member whose value MUST be a string containing a
   JSON-Pointer [RFC6901] value referencing the name value pair that is
   to be tested.  If the "path" member is not specified within the
   predicate object, it's value is assumed to be an empty string.

   Second Order Predicates MUST have exactly one "apply" member whose
   value is a JSON Array containing one or more First or Second Order
   Predicate Objects.



Snell                    Expires March 28, 2014                 [Page 4]

Internet-Draft               JSON Predicates              September 2013


   Additional members can be required depending on the specific
   predicate operation.  All other members not explicitly defined by
   this specification MUST be ignored.

   Note that the ordering of members in JSON objects is not significant;
   therefore the following operations are equivalent:

   {"op": "contains", "path": "/a/b/c", "value": "ABC"}
   {"path": "/a/b/c", "op": "contains", "value": "ABC"}
   {"value": "ABC", "path": "/a/b/c", "op": "contains"}


2.1.  Predicate Context

   All JSON Predicates are evaluated against a given base context.  The
   nature of this context is dependent entirely on the application
   within which JSON Predicates is being used.  For instance, when used
   together with JSON Patch, the JSON Predicate operations are evaluated
   relative to the JSON document that is the target of the JSON Patch
   operation.

   Although Predicate Objects use JSON Pointer references to identify
   values against which a predicate operation is evaluated, the base
   context is not required to be a JSON object or array.  In such cases,
   however, it is the responsibility of the application implementation
   to determine how to interpret the JSON Pointer reference relative to
   the base context.

2.2.  First Order Predicates

2.2.1.  "contains" and "contains-"

   The "contains" predicate evaluates as true if the referenced element
   is defined and has a value whose string representation contains the
   exact sequence of characters given by the predicate object's "value"
   member.

   For example, given the JSON document:

   {
     "a": {
       "b": "This is a test"
     }
   }


   The following predicate would evaluate as "true":




Snell                    Expires March 28, 2014                 [Page 5]

Internet-Draft               JSON Predicates              September 2013


   {
     "op": "contains",
     "path": "/a/b",
     "value": " is a "
   }


   By default, character matching MUST be performed in a case-sensitive
   manner.  To override this default behavior and perform case-
   insensitive matching, use "contains-" as the value of "op".

   For instance, the following will evaluate as "true":

   {
     "op": "contains-",
     "path": "/a/b/",
     "value": " Is A "
   }


2.2.2.  defined

   The "defined" predicate evaluates as true if the referenced element
   exists within the target context.

   For example, given the JSON document:

   {
   "a": {
     "b": null
    }
   }


   The following predicate would evaluate as "true" because the path "/a
   /b" exist within the document despite being explicitly set to null:

   {
     "op": "defined",
     "path": "/a/b"
   }


   The following predicate would evaluate as "false" because the path "/
   a/c" does exist within the document.






Snell                    Expires March 28, 2014                 [Page 6]

Internet-Draft               JSON Predicates              September 2013


   {
     "op": "defined",
     "path": "/a/c"
   }


2.2.3.  ends

   The "ends" predicate evaluates as true if the referenced element is
   defined and has a value whose string representation ends with the
   exact sequence of characters given by the predicate object's "value"
   member.

   For example, given the JSON document:

   {
     "a": {
       "b": "This is a test"
     }
   }


   The following predicate would evaluate as "true":

   {
     "op": "ends",
     "path": "/a/b",
     "value": " test"
   }


   By default, character matching MUST be performed in a case-sensitive
   manner.  To override this default behavior and perform case-
   insensitive matching, use "ends-" as the value of "op".

   For instance, the following will evaluate as "true":

   {
     "op": "ends-",
     "path": "/a/b/",
     "value": " TEST"
   }


2.2.4.  in

   The "in" predicate evaluates as true if the referenced element
   specifies a value exactly equal to one of the members of a JSON array



Snell                    Expires March 28, 2014                 [Page 7]

Internet-Draft               JSON Predicates              September 2013


   provided by the predicate's "value" member.  Equality is determined
   following the sames rules specified for the JSON Patch "test"
   operation in [RFC6902], Section 4.6, with one exception given for
   optional case-insensitive comparisons.

   For example, given the JSON document:

   {
     "a": {
       "b": 10
     }
   }


   The following will evaluate as "true":

   {
     "op": "in",
     "path": "/a/b",
     "value": [1, "foo", 10, {"z":"y"}]
   }


   The value specified for the "value" member MUST be a JSON Array.

   By default, when comparing string values, character matching MUST be
   performed in a case-sensitive manner.  To override this default
   behavior and perform case-insensitive matching, use "in-" as the
   value of "op".

2.2.5.  less

   The "less" predicate evaluates as true if the referenced element is
   defined and specifies a number whose value is less than that
   specified by the predicate object's "value" member.

   For example, given the JSON document:

   {
     "a": {
       "b": 10
     }
   }


   The following will evaluate as "true":





Snell                    Expires March 28, 2014                 [Page 8]

Internet-Draft               JSON Predicates              September 2013


   {
     "op": "less",
     "path": "/a/b",
     "value": 15
   }


2.2.6.  matches

   The "matches" predicate evaluates as true if the referenced element
   is defined and has a value whose completely string representation
   matches the regular expression provided by the predicate object's
   "value" member.

   For example, given the JSON document:

   {
     "a": {
       "b": "this is a test"
     }
   }


   The following evalutes as "true":

   {
     "op": "matches",
     "path": "/a/b",
     "value": "[\\w\\s]*"
   }


   The predicate's matching pattern is expressed as a string value
   conforming to the JavaScript Regular Expression syntax (see [1]).

   By default, matching against the regular expression MUST be performed
   in a case-sensitive manner.  To override this default behavior and
   perform case-insensitive matching, use "matches-" as the value of
   "op".  Using this alternative is equivalent to using the "i" modifier
   flag within the JavaScript Regular Expression syntax (e.g. "/\w\s/*/
   i").

   For instance:








Snell                    Expires March 28, 2014                 [Page 9]

Internet-Draft               JSON Predicates              September 2013


   {
     "op": "matches-",
     "path": "/a/b/",
     "value": "[\\w\\s]*"
   }


2.2.7.  more

   The "more" predicate evaluates as true if the referenced element is
   defined and specifies a number whose value is greater than that
   specified by the predicate object's "value" member.

   For example, given the JSON document:

   {
     "a": {
       "b": 10
     }
   }


   The following will evaluate as "true":

   {
     "op": "more",
     "path": "/a/b",
     "value": 5
   }


2.2.8.  starts

   The "starts" predicate evaluates as true if the referenced element is
   defined and has a value whose string representation begins with the
   exact sequence of characters given by the predicate object's "value"
   member.

   For example, given the JSON document:

   {
     "a": {
       "b": "This is a test"
     }
   }






Snell                    Expires March 28, 2014                [Page 10]

Internet-Draft               JSON Predicates              September 2013


   The following predicate would evaluate as "true":

   {
     "op": "starts",
     "path": "/a/b",
     "value": "This "
   }


   By default, character matching MUST be performed in a case-sensitive
   manner.  To override this default behavior and perform case-
   insensitive matching, use "starts-" as the value of "op".

   For instance, the following will evaluate as "true":

   {
     "op": "starts-",
     "path": "/a/b/",
     "value": "this "
   }


2.2.9.  test

   The JSON Patch "test" operation, as defined by [RFC6902],
   Section 4.6, can be used as a First Order Predicate operation.  It
   evaluates as true if the referenced element exists and specifies a
   value that is exactly equal to that provided by the predicate's
   "value" member.  The rules for evaluating equality are identical to
   those defined within [RFC6902], Section 4.6, with one exception given
   for optional case-insensitive comparisons.

   For example, given the JSON document:

   {
     "a": {
       "b": "this is a test"
     }
   }












Snell                    Expires March 28, 2014                [Page 11]

Internet-Draft               JSON Predicates              September 2013


   The following predicate would evaluate as "true"

   {
     "op": "test",
     "path": "/a/b",
     "value": "this is a test"
   }


   By default, when comparing string values, character matching MUST be
   performed in a case-sensitive manner To override this default
   behavior and perform case-insensitive matching, use "test-" as the
   value of "op".

2.2.10.  type

   The "type" predicate evaluates as true if the referenced element
   exists and specifies a value whose value type is equal to that
   specified by the predicate's "value" member.

   The "value" member MUST specify one of: "number", "string",
   "boolean", "object", "array", "null", "undefined", "date", "date-
   time", "time", "lang", "lang-range", "iri" or "absolute-iri".

   For example, given the JSON document:

   {
     "a": {
       "b": "this is a test",
       "c": [1,2,3]
     }
   }


   The following predicate would evaluate as "true"

   {
     "op": "type",
     "path": "/a/b",
     "value": "string"
   }


   When evaluating the type of a value, the following rules apply:

   o  If the "value" member specifies "number", the type predicate will
      evaluate as true if the value referenced by the "path" member is a
      JSON number.



Snell                    Expires March 28, 2014                [Page 12]

Internet-Draft               JSON Predicates              September 2013


   o  If the "value" member specifies "string", the type predicate will
      evaluate as true if the value referenced by the "path" member is a
      JSON string.

   o  If the "value" member specifies "boolean", the type predicate will
      evaluate as true if the value referenced by the "path" member is a
      JSON boolean.

   o  If the "value" member specifies "object", the type predicate will
      evaluate as true if the value referenced by the "path" member is a
      JSON object.

   o  If the "value" member specifies "array", the type predicate will
      evaluate as true if the value referenced by the "path" member is a
      JSON array.

   o  If the "value" member specifies "null", the type predicate will
      evaluate as true if the value referenced by the "path" member is a
      JSON null.

   o  If the "value" member specifies "undefined", the type predicate
      will evaluate as true if the member referenced by the "path"
      member does not exist.

   o  If the "value" member specifies "date", the type predicate will
      evaluate as true if the value referenced by the "path" member is a
      JSON string conforming to the [RFC3339] "full-date" construct.

   o  If the "value" member specifies "time", the type predicate will
      evaluate as true if the value referenced by the "path" member is a
      JSON string conforming to the [RFC3339] "full-time" construct.

   o  If the "value" member specifies "date-time", the type predicate
      will evaluate as true if the value referenced by the "path" member
      is a JSON string conforming to the [RFC3339] "date-time"
      construct.

   o  If the "value" member specifies "lang", the type predicate will
      evaluate as true if the value referenced by the "path" member is a
      JSON string conforming to the [RFC5646] "Language-Tag" construct.

   o  If the "value" member specifies "lang-range", the type predicate
      will evaluate as true if the value referenced by the "path" member
      is a JSON string conforming to the [RFC4647] "language-range"
      construct.






Snell                    Expires March 28, 2014                [Page 13]

Internet-Draft               JSON Predicates              September 2013


   o  If the "value" member specifies "iri", the type predicate will
      evaluate as true if the value referenced by the "path" member is a
      JSON string conforming to the [RFC3987] "IRI-reference" construct.

   o  If the "value" member specifies "absolute-iri", the type predicate
      will evaluate a true if the value referenced by the "path" member
      is a JSON string conforming to the [RFC3987] "IRI" construct.

2.2.11.  undefined

   The "undefined" predicate evaluates as true if the referenced element
   does not exist within the target context.

   For example, given the JSON document:

   {
   "a": {
     "b": null
    }
   }


   The following predicate would evaluate as "true" because the path "/a
   /c" does not exist within the document:

   {
     "op": "undefined",
     "path": "/a/c"
   }


   However, the following predicate would evaluate as "false" because
   the path "/a/b" does exist within the document, despite specifying an
   explicit null value.

   {
     "op": "undefined",
     "path": "/a/b"
   }


2.3.  Second-Order Predicates

   Second Order Predicates are defined as sets of one or more
   subordinate First and Second Order Predicates.

   All Second Order Predicates MAY contain a "path" member whose value
   specifies a root path prefix for all contained predicates.  If the



Snell                    Expires March 28, 2014                [Page 14]

Internet-Draft               JSON Predicates              September 2013


   "path" member is not specified, it's value is assumed to be an empty
   string.  For example, given the JSON document:

   {
     "a": {
       "b": {
         "c": "ABC!"
       }
     }
   }


   The following would evaluate as true because the path "/a/b/c" is
   defined.

   {
     "op": "and",
     "path": "/a/b",
     "apply": [
       {
         "op": "defined",
         "path": "/c"
       }
     ]
   }


   The above example is equivalent to:

    {
      "op": "and",
      "apply": [
        {
          "op": "defined",
          "path": "/a/b/c"
        }
      ]
    }


2.3.1.  and

   The "and" predicate evaluates as "true" if all of it's contained set
   of predicate operations evaluate as "true".

   For example, given the JSON document:





Snell                    Expires March 28, 2014                [Page 15]

Internet-Draft               JSON Predicates              September 2013


   {
     "a" : {
       "b" : "foo",
       "c" : {
         "d": 10
       }
     }
   }


   The following would evaluate as "true" because the element "/a/b" is
   defined and the value of element "/a/c/d" is less than 15.

   {
     "op": "and",
     "apply" [
       {
         "op": "defined",
         "path": "/a/b"
       },
       {
         "op": "less",
         "path": "/a/c/d",
         "value": 15
       }
     ]
   }


   However, the following would evaluate as "false" because while the
   element "/a/c" exists, the value of that element is not a string.

   {
     "op": "and",
     "apply": [
       {
         "op": "test",
         "path": "/a/c"
       },
       {
         "op": "type",
         "path": "/a/c",
         "value": "string"
       }
     ]
   }





Snell                    Expires March 28, 2014                [Page 16]

Internet-Draft               JSON Predicates              September 2013


2.3.2.  not

   The "not" predicate evaluates as "true" if all of it's contained set
   of predicate operations evaluate as "false".

   For example, given the JSON document:

   {
     "a" : {
       "b" : "foo",
       "c" : {
         "d": 10
       }
     }
   }


   The following would evaluate as "true" because the element "/a/b/e"
   is undefined and the value of element "/a/c/d" is not less than 5.

   {
     "op": "not",
     "apply": [
       {
         "op": "defined",
         "path": "/a/b/e"
       },
       {
         "op": "less",
         "path": "/a/c/d",
         "value": 5
       }
     ]
   }


   However, the following would evaluate as "false" because the element
   "/a/c" exists and the value for element "/a/b" begins with the letter
   "f"












Snell                    Expires March 28, 2014                [Page 17]

Internet-Draft               JSON Predicates              September 2013


   {
     "op": "not",
     "apply": [
       {
         "op": "undefined",
         "path": "/a/c"
       },
       {
         "op": "starts",
         "path": "/a/b",
         "value": "f"
       }
     ]
   }


2.3.3.  or

   The "or" predicate evaluates as "true" if at least one of it's
   contained set of predicate operations evaluate as "true".

   For example, given the JSON document:

   {
     "a" : {
       "b" : "foo",
       "c" : {
         "d": 10
       }
     }
   }


   The following would evaluate as "true" because the element "/a/b" is
   defined.
















Snell                    Expires March 28, 2014                [Page 18]

Internet-Draft               JSON Predicates              September 2013


   {
     "op": "or",
     "apply": [
       {
         "op": "defined",
         "path": "/a/b"
       },
       {
         "op": "less",
         "path": "/a/c/d",
         "value": 5
       }
     ]
   }


   However, the following would evaluate as "false" because neither
   elements "/a/e" or "/a/f" exist.

   {
     "op": "or",
     "apply": [
       {
         "op": "test",
         "path": "/a/e"
       },
       {
         "op": "test",
         "path": "/a/f"
       }
     ]
   }


2.3.4.  Nesting Second Order Predicates

   Second Order Predicates can be combined in a variety of ways to
   define more complex test operations.  For example:

   {
     "op": "or",
     "path": "/a/b",
     "apply": [
       {
         "op": "not",
         "path": "/c",
         "apply": [
           {"op": "undefined"},



Snell                    Expires March 28, 2014                [Page 19]

Internet-Draft               JSON Predicates              September 2013


           {"op": "starts", "value": "f"}
         ]
       },
       {
         "op": "not",
         "path": "/d",
         "apply": [
           {"op": "defined"},
           {"op": "type", "value": "number"}
         ]
       }
     ]
   }


2.4.  Error Handling

   When an error condition is encounted during the processing of a JSON
   Predicate, the predicate MUST evaluate as false.  Whether or not the
   error condition is reported is dependent on the specific requirements
   of the application within which JSON Predicates are being used.

   Error conditions can arise in each of the following conditions:

   o  JSON Predicate Objects contained within a document fail to conform
      to any normative requirement of this specification, or

   o  The Predicate Object specifies an unknown predicate operation, or

   o  The Predicate Object specifies a JSON Pointer referencing a value
      that does not exist and the specified Predicate operation is not
      specifically intended to test for the absence of a value (i.e. the
      "undefined" and "defined" predicates), or

   o  A First Order Predicate Object specifies a predicate operation
      that requires a "value" member providing the condition to test but
      no "value" member is provided.

   o  The "value" member given for a given predicate operation is of an
      unexpected or unsupported type for that operation (e.g.
      specifying a string value for the "more" and "less" predicate
      operations).









Snell                    Expires March 28, 2014                [Page 20]

Internet-Draft               JSON Predicates              September 2013


2.5.  Using JSON Predicates within JSON Patch Documents

   While JSON Predicate objects can be used in a variety of
   applications, the syntax has been specifically designed for
   compatibility with the JSON Patch Document format.  JSON Predicate
   objects MAY be used directly within a JSON Patch Document as tests to
   evaluate whether or not the application of a set of patch operations
   should succeed or fail.

   Because of requirements defined by the JSON Patch specification, when
   Second Order Predicates are used as patch test operations within a
   JSON Patch document, the "path" member MUST be specified.  The value
   of the "path" member MAY be an empty string.

   For example, given the following JSON document:

   {
     "a": {
       "b": {
         "c": "123"
       }
     }
   }


   The following JSON Patch + JSON Predicates document will first test
   that the path "/a/b/c" references a string value matching the given
   regular expression prior to replacing that value:

   [
     {
       "op": "and",
       "path": "/a/b/c",
       "apply": [
         {"op": "type", "value": "string"},
         {"op": "matches", "value": "\\d{3}"}
       ]
     },
     {
       "op": "replace",
       "path": "/a/b/c",
       "value": "ABC"
     }
   ]


   When a JSON Predicate object within a JSON Patch document evaluates
   as false, processing of the JSON Patch Document MUST be handled



Snell                    Expires March 28, 2014                [Page 21]

Internet-Draft               JSON Predicates              September 2013


   exactly the same as an unsuccessful JSON Patch operation would be
   handled as defined in JSON-PATCH [RFC6902].  Specifically, processing
   of the JSON Patch document SHOULD terminate and application of the
   entire patch document SHALL NOT be deemed successful.

   The MIME media type "application/json-patch-test" is used to identify
   JSON Patch documents that contain predicates.

   For example:

     PATCH /some-document HTTP/1.1
     Host: example.org
     Content-Type: application/json-patch-test+json

     [
       {
         "op": "matches",
         "path": "/a/b/c",
         "value": "\\d{3}"
       },
       {
         "op": "replace",
         "path": "/a/b/c",
         "value": "ABC"
       }
     ]


   JSON Patch implementations that do not implement or recognize JSON
   Predicate objects will treat them as unknown patch operations that
   will cause evaluation of the Patch document to fail.

2.5.1.  Conditional Patch Operations

   In addition to being included as top-level operations within a JSON
   Patch Document, Predicate objects can be used to make conditional
   JSON Patch Operations through the use of the optional "if" and
   "unless" properties.

   For instance, in the following example, the value at path "/a/b/0" is
   only removed if the value at path "/a/b" is an Array.

     PATCH /some-document HTTP/1.1
     Host: example.org
     Content-Type: application/json-patch-test+json

     [
       {



Snell                    Expires March 28, 2014                [Page 22]

Internet-Draft               JSON Predicates              September 2013


         "op": "remove",
         "path": "/a/b/0",
         "if": {
           "op": "type",
           "path": "/a/b",
           "value": "array"
         }
       }
     ]


   Unlike a typical JSON Patch operation, however, the processing of the
   complete Patch document does not fail if the stated condition is not
   met.  The operation is considered to be successful even if the stated
   modification is not performed and processing will continue.

   In the following example, the value at path "/a/b/0" is removed
   unless the value at path "/a/b" is undefined and missing.

     PATCH /some-document HTTP/1.1
     Host: example.org
     Content-Type: application/json-patch-test

     [
       {
         "op": "remove",
         "path": "/a/b/0",
         "unless": {
           "op": "undefined",
           "path": "/a/b"
         }
       }
     ]


   In a normal JSON Patch document without using predicates, processing
   of the "remove" operation would cease and an error would be reported.
   Using the "if" and "unless" properties, the error condition can be
   avoided and compensated for using a combination of operations.  For
   instance, in the following example the patch document ensures that
   the "/a/b" path exists and is an array before attempting to add new
   items.  If the existing value of "/a/b" is not an array, it's value
   is changed to an array prior to adding items, ensuring that the
   second "add" operation will succeed.

     PATCH /some-document HTTP/1.1
     Host: example.org
     Content-Type: application/json-patch-test



Snell                    Expires March 28, 2014                [Page 23]

Internet-Draft               JSON Predicates              September 2013


     [
       {
         "op": "add",
         "path": "/a/b",
         "value": [],
         "unless": {
           "op": "and",
           "apply": [
             {"op": "defined"},
             {"op": "type", "value": "array"}
           ]
         }
       },
       {
         "op": "add",
         "path": "/a/b/-",
         "value": "ABC"
       }
     ]


   The "if" and "unless" properties can be used with any JSON Patch
   operation in a document using the "application/json-patch-test+json"
   content type.  The "if" and "unless" properties MUST NOT be used in
   JSON Predicate objects.

3.  IANA Considerations

   The Internet media type for a JSON Patch document containing JSON
   Predicate objects is application/json-patch-test+json.

      Type name:  application

      Subtype name:  json-patch-test+json

      Required parameters:  none

      Optional parameters:   none

      Encoding considerations:  binary

      Security considerations:
         See Security Considerations in Section 4

      Interoperability considerations:  N/A

      Published specification:
         [this memo]



Snell                    Expires March 28, 2014                [Page 24]

Internet-Draft               JSON Predicates              September 2013


      Applications that use this media type:
         Applications that manipulate JSON documents.

      Additional information:

         Magic number(s):  N/A

         File extension(s):  .json-patch-test

         Macintosh file type code(s):  TEXT

      Person & email address to contact for further information:
         James M Snell <jasnell@gmail.com>

      Intended usage:  COMMON

      Restrictions on usage:  none

      Author:  James M Snell <jasnell@gmail.com>

      Change controller:  IETF


4.  Security Considerations

   JSON Predicate objects do not, by themselves, introduce any
   particular security concerns.  Note that JSON documents that consist
   of an arbitrary number of nested Second Order Predicate objects can
   have a detrimental impact on overall performance and could be
   leveraged by a malicious entity as part of a denial of service
   attack.

5.  References

5.1.  Normative References

   [RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
              Requirement Levels", BCP 14, RFC 2119, March 1997.

   [RFC3339]  Klyne, G., Ed. and C. Newman, "Date and Time on the
              Internet: Timestamps", RFC 3339, July 2002.

   [RFC3987]  Duerst, M. and M. Suignard, "Internationalized Resource
              Identifiers (IRIs)", RFC 3987, January 2005.

   [RFC4627]  Crockford, D., "The application/json Media Type for
              JavaScript Object Notation (JSON)", RFC 4627, July 2006.




Snell                    Expires March 28, 2014                [Page 25]

Internet-Draft               JSON Predicates              September 2013


   [RFC4647]  Phillips, A. and M. Davis, "Matching of Language Tags",
              BCP 47, RFC 4647, September 2006.

   [RFC5646]  Phillips, A. and M. Davis, "Tags for Identifying
              Languages", BCP 47, RFC 5646, September 2009.

   [RFC6901]  Bryan, P., Zyp, K., and M. Nottingham, "JavaScript Object
              Notation (JSON) Pointer", RFC 6901, April 2013.

5.2.  Informative References

   [RFC6902]  Bryan, P. and M. Nottingham, "JavaScript Object Notation
              (JSON) Patch", RFC 6902, April 2013.

Appendix A.  Non-Normative Example Implementation (Ruby)

   Following is a non-normative simple example implementation of JSON
   Predicates, JSON Pointer and JSON Patch written in Ruby.  This
   purpose of this code is to simply illustrate the basic function of
   JSON Predicates within a specific context.  It should not be
   considered to be a reference implementation and has not been
   thoroughly tested for every possible case.  The code is offered under
   the Apache v2.0 license without warranty of any kind and may be used
   for any purpose so long as proper attribution is maintained.

   ####################################
   # JSON Tools                       #
   #   Implementation of JSON Patch,  #
   #   Pointer and Predicates         #
   #                                  #
   # Author:  James M Snell           #
   #          (jasnell@gmail.com)     #
   # Date:    2013-09-24              #
   # License: Apache v2.0             #
   ####################################

   require 'json'

   class Hash
     # A fairly inefficient means of
     # generating a deep copy of the
     # hash; but it ensures that our
     # hash conforms to the JSON spec
     # and does not contain any cycles
     def json_deep_copy
       JSON.parse to_json
     end




Snell                    Expires March 28, 2014                [Page 26]

Internet-Draft               JSON Predicates              September 2013


     def insert loc,val
       self[loc] = val
     end

     def delete_at loc
       self.delete loc
     end

   end

   module JsonTools

     def self.fix_key obj, key
       if Array === obj
         idx = Integer key
         fail if not (0...obj.length).cover? idx
         key = idx
       end
       key
     end

     class Pointer

       # Raised when an error occurs during the
       # evaluation of the pointer against a
       # given context
       class PointerError < StandardError; end

       def initialize path
         @parts = path.split('/').drop(1).map { |p|
             p.gsub(/\~1/, '/').gsub(/\~0/, '~')
           }
         @last = @parts.pop
       end

       # Returns the last segment of the JSON Pointer
       def last; @last; end

       # Evaluates the pointer against the given
       # context hash object and returns the
       # parent. That is, if the Pointer is
       # "/a/b/c", parent will return the object
       # referenced by "/a/b", or nil if that
       # object does not exist.
       def parent context
         @parts.reduce(context) do |o, p|
           o[(o.is_a?(Array) ? p.to_i : p)]
         end



Snell                    Expires March 28, 2014                [Page 27]

Internet-Draft               JSON Predicates              September 2013


       rescue
         raise PointerError
       end
       alias :[] :parent

       # Enumerates down the pointer path, yielding
       # to the given block each name, value pair
       # specified in the path, halting at the first
       # nil value encountered. The required block
       # will be passed two parameters. The first is
       # the accessor name, the second is the value.
       # For instance, given the hash {'a'=>{'b'=>{'c'=>123}}},
       # and the pointer "/a/b/c", the block will be
       # called three times, first with ['a',{'b'=>{'c'=>123}}],
       # next with ['b',{'c'=>123}], and finally with
       # ['c',123].
       def walk context
         p = @parts.reduce(context) do |o,p|
           n = o[(o.is_a?(Array) ? p.to_i : p)]
           yield p, n
           return if NilClass === n # exit the loop if the object is nil
           n
         end
         key = JsonTools.fix_key(p,@last)
         yield key, (!p ? nil : p[key])
       end

       # Returns the specific value identified by this
       # pointer, if any. Nil is returned if the path
       # does not exist. Note that this does not differentiate
       # between explicitly null values or missing paths.
       def value context
         parent = parent context
         parent[JsonTools.fix_key(parent,@last)] unless !parent
       end

       # Alternative to value that raises a PointerError
       # if the referenced path does not exist.
       def value_with_fail context
         parent = parent context
         fail if !parent
         parent.fetch(JsonTools.fix_key(parent,@last))
       rescue
         raise PointerError
       end

       # True if the referenced path exists
       def exists? context



Snell                    Expires March 28, 2014                [Page 28]

Internet-Draft               JSON Predicates              September 2013


         p = parent context
         if Array === p
           (0...p.length).cover? Integer(@last)
         else
           p.has_key? @last
         end
       rescue
         false
       end

     end

     class Patch

       PATCH_OPERATIONS = {}

       class InvalidPatchDocumentError < StandardError; end
       class FailedOperationError < StandardError; end

       def initialize ops, with_predicates=false
         # Parse JSON if necessary
         if ops.is_a?(String) || ops.respond_to?(:read)
           ops = JSON.load(ops)
         end
         fail unless Array === ops
         @ops = ops
         # Should we include the JSON Predicate operations?
         # Off by default
         extend Predicate if with_predicates
       rescue
         raise InvalidPatchDocumentError
       end

       # Initialize a new Patch object with
       # JSON Predicate Operations enabled
       def self.new_with_predicates ops
         new ops, true
       end

       # Apply the patch to the given target hash
       # object. Note that the target will be
       # modified in place and changes will not
       # be reversable in the case of failure.
       def apply_to! target
         @ops.each_with_object(target) do |operation, target|
           raise 'Invalid Operation' unless operation.key?('op')
           op = operation['op']
           ic = op.slice!(-1) if op[-1] == '-'



Snell                    Expires March 28, 2014                [Page 29]

Internet-Draft               JSON Predicates              September 2013


           operation['ignore_case'] = ic != nil
           PO = PATCH_OPERATIONS[op.to_sym]
           PO[operation, target] rescue raise 'Invalid Operation'
         end
       end

       # Apply the patch to a copy of the given
       # target hash. The new, modified hash
       # will be returned.
       def apply_to target
         apply_to! target.json_deep_copy
       end

       private

       # Define the various core patch operations
       class << Patch

         def add params, target
           ptr = Pointer.new params['path']
           obj = ptr[target]
           fail if not (Array === obj || Hash === obj)
           if (Array === obj && ptr.last == '-')
             obj.insert -1,params['value']
           else
             obj.insert JsonTools.fix_key(obj,ptr.last),params['value']
           end
         rescue
            raise FailedOperationError
         end

         def remove params, target
           ptr = Pointer.new params['path']
           return if not ptr.exists? target #it's gone, just ignore..
           obj  = ptr[target]
           obj.delete_at JsonTools.fix_key(obj,ptr.last)
         rescue
           raise FailedOperationError
         end

         def move params, target
           move_or_copy params, target, true
         end

         def copy params, target
           move_or_copy params, target, false
         end




Snell                    Expires March 28, 2014                [Page 30]

Internet-Draft               JSON Predicates              September 2013


         def move_or_copy params, target, move=false
           from = Pointer.new params['from']
           to = Pointer.new params['path']
           fail if !from.exists?(target) #|| to.exists?(target)
           obj = from[target]
           val = obj[JsonTools.fix_key(obj,from.last)]
           remove(({'path'=>params['path']}), target) if move
           add ({'path'=>params['to'],'value'=>val}), target
         rescue
           raise FailedOperationError
         end

         def replace params, target
           ptr = Pointer.new params['path']
           fail if not ptr.exists? target
           obj = ptr[target]
           obj[JsonTools.fix_key(obj,ptr.last)] = params['value']
         rescue
           raise FailedOperationError
         end

         def test params, target
           ptr = Pointer.new(params['path'])
           fail if not ptr.exists? target
           obj = ptr[target]
           val = obj[JsonTools.fix_key(obj,ptr.last)]
           fail unless val == params['value']
         rescue
           raise FailedOperationError
         end

       end

       # Specify the Patch Operations
       [:add,:remove,:replace,:move,:copy,:test].each {
         |x| PATCH_OPERATIONS[x] = lambda(&method(x))
       }

       public

       def register_op sym, op
         PATCH_OPERATIONS[sym] = op
       end

     end # End Patch Class

     # Define the Predicate methods for use with the Patch object
     module Predicate



Snell                    Expires March 28, 2014                [Page 31]

Internet-Draft               JSON Predicates              September 2013


       def self.string_check params, target, &block
         ptr = Pointer.new params['path']
         return false if !ptr.exists?(target)
         val = ptr.value target
         return false unless String === val
         ignore_case = params['ignore_case']
         test_val = params['value']
         if ignore_case
           test_val.upcase!
           val.upcase!
         end
         yield val, test_val
       end

       def self.number_check params, target, &block
         ptr = Pointer.new params['path']
         return false if !ptr.exists?(target)
         val = ptr.value target
         test_val = params['value']
         return false unless (Numeric === val && Numeric === test_val)
         yield val, test_val
       end

       def self.contains params, target
         string_check(params,target) {|x,y| x.include? y }
       end

       def self.defined params, target
         ptr = Pointer.new params['path']
         ptr.exists?(target)
       end

       def self.ends params, target
         string_check(params,target) {|x,y| x.end_with? y }
       end

       def self.matches params, target
         ptr = Pointer.new params['path']
         return false if !ptr.exists?(target)
         val = ptr.value target
         return false unless String === val
         ignore_case = params['ignore_case']
         test_val = params['value']
         regex = ignore_case ?
           Regexp.new(test_val, Regexp::IGNORECASE) :
           Regexp.new(test_val)
         regex.match val
       end



Snell                    Expires March 28, 2014                [Page 32]

Internet-Draft               JSON Predicates              September 2013


       def self.less params, target
         number_check(params,target) {|x,y| x < y}
       end

       def self.more params, target
         number_check(params,target) {|x,y| x > y}
       end

       def self.starts params, target
         string_check(params,target) {|x,y| x.start_with? y }
       end

       def self.type params, target
         ptr = Pointer.new params['path']
         test_val = params['value']
         if !ptr.exists? target
           test_val == 'undefined'
         else
           return false if !ptr.exists?(target)
           val = ptr.value target
           case test_val
           when 'number'
             Numeric === val
           when 'string'
             String === val
           when 'boolean'
             TrueClass === val || FalseClass === val
           when 'object'
             Hash === val
           when 'array'
             Array === val
           when 'null'
             NilClass === val
           else
             false
           end
         end
       end

       def self.undefined params, target
         ptr = Pointer.new params['path']
         !ptr.exists?(target)
       end

       def self.and params, target
         preds = params['apply']
         return false unless preds.all? {|pred|
           op = pred['op']



Snell                    Expires March 28, 2014                [Page 33]

Internet-Draft               JSON Predicates              September 2013


           ic = op.slice! -1 if op[-1] == '-'
           pred['ignore_case'] = ic != nil
           PREDICATES[op.to_sym][pred,target] rescue return false
         }
         true
       end

       def self.not params, target
         preds = params['apply']
         return false unless preds.none? {|pred|
           op = pred['op']
           ic = op.slice! -1 if op[-1] == '-'
           pred['ignore_case'] = ic != nil
           PREDICATES[op.to_sym][pred,target] rescue return false
         }
         true
       end

       def self.or params, target
         preds = params['apply']
         return false unless preds.any? {|pred|
           op = pred['op']
           ic = op.slice! -1 if op[-1] == '-'
           pred['ignore_case'] = ic != nil
           PREDICATES[op.to_sym][pred,target] rescue return false
         }
         true
       end

       PREDICATES = {}
       [:contains,
        :defined,
        :ends,
        :less,
        :matches,
        :more,
        :starts,
        :type,
        :undefined,
        :and,
        :not,
        :or].each {|x|
           PREDICATES[x] = lambda(&method(x))
         }

       def self.extended other

         PREDICATES.each_pair {|x,y|



Snell                    Expires March 28, 2014                [Page 34]

Internet-Draft               JSON Predicates              September 2013


           other.register_op x, ->(params,target) {
           raise Patch::FailedOperationError unless y.call params,target
           }
         }

       end
     end

   end # End Module


   The following illustrates how the Ruby module is used:







































Snell                    Expires March 28, 2014                [Page 35]

Internet-Draft               JSON Predicates              September 2013


   require 'jsontools'
   include JsonTools

   my_hash = JSON.parse %Q/
     {
       "a": {
         "b": {
           "c": "123!ABC"
         }
       }
     }
   /

   my_patch = JSON.parse %Q!
     [
       {
         "op": "contains",
         "path": "/a/b/c",
         "value": "ABC"
       },
       {
         "op": "replace",
         "path": "/a/b/c",
         "value": 123
       }
     ]
   !

   patch = Patch.new_with_predicates my_patch

   # create new modified hash
   new_hash = patch.apply_to my_hash

   # edit hash in place
   patch.apply_to! my_hash


Author's Address

   James M Snell

   Email: jasnell@gmail.com









Snell                    Expires March 28, 2014                [Page 36]