One document matched: draft-snell-json-test-07.xml


<?xml version="1.0"?> 
<!DOCTYPE rfc SYSTEM "rfc2629.dtd" [ 
  <!ENTITY rfc2119 PUBLIC '' 'http://xml.resource.org/public/rfc/bibxml/reference.RFC.2119.xml'>
  <!ENTITY rfc4267 PUBLIC '' 'http://xml.resource.org/public/rfc/bibxml/reference.RFC.4267.xml'>  
  <!ENTITY rfc3339 PUBLIC '' 'http://xml.resource.org/public/rfc/bibxml/reference.RFC.3339.xml'>
  <!ENTITY rfc5646 PUBLIC '' 'http://xml.resource.org/public/rfc/bibxml/reference.RFC.5646.xml'>
  <!ENTITY rfc4647 PUBLIC '' 'http://xml.resource.org/public/rfc/bibxml/reference.RFC.4647.xml'>
  <!ENTITY rfc3987 PUBLIC '' 'http://xml.resource.org/public/rfc/bibxml/reference.RFC.3987.xml'>
  <!ENTITY rfc4627 PUBLIC '' 'http://xml.resource.org/public/rfc/bibxml/reference.RFC.4627.xml'>
  <!ENTITY rfc6902 PUBLIC '' 'http://xml.resource.org/public/rfc/bibxml/reference.RFC.6902.xml'>
  <!ENTITY rfc6901 PUBLIC '' 'http://xml.resource.org/public/rfc/bibxml/reference.RFC.6901.xml'>
]>
<?rfc toc="yes"?> 
<?rfc strict="yes"?> 
<?rfc symrefs="yes" ?> 
<?rfc sortrefs="yes"?> 
<?rfc compact="yes"?> 
<rfc category="info" ipr="trust200811" docName="draft-snell-json-test-07"> 
  <front> 
    <title abbrev="JSON Predicates"> 
      JSON Predicates
    </title> 
 
    <author initials="J.M." surname="Snell" fullname="James M Snell"> 
      <address> 
        <email>jasnell@gmail.com</email> 
      </address> 
    </author> 
    
    <date month="September" year="2013" /> 
 
    <area>Applications</area> 
    <workgroup>Individual Submission</workgroup> 
    <keyword>I-D</keyword> 
    <keyword>json</keyword>
    <keyword>predicate</keyword>
 
    <abstract> 
      <t>JSON Predicates defines a syntax for serializing various
      predicate expressions as JSON Objects.</t> 
    </abstract> 
 
  </front> 
  
  <middle> 

<section anchor="intro" title="Introduction"> 

  <t>
    This specification defines JSON Predicates, a JSON-based <xref target="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 <xref target="RFC6902">JSON Patch</xref>, as a means of 
    incorporating conditional processing.
  </t>
  
  <t>
    JSON Predicates can be used, for instance, to extend a 
    <xref target="RFC6902">JSON Patch</xref> document to provide for a 
    broader range of conditional processing options not currently supported 
    by JSON Patch.
  </t>
  
  <figure><preamble>Example: Given a source JSON document</preamble>
  <artwork>
  {
    "a": {
      "b": {
        "c": "ABC!XYZ"
      }
    }
  }
  </artwork></figure>
  
  <figure><preamble>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.</preamble><artwork>
  [
    {
      "op": "and",
      "path": "/a/b/c",
      "apply": [
        {
          "op": "type", 
          "value": "string"
        },
        {
          "op": "contains", 
          "value": "ABC"
        }
      ]
    },
    {
      "op": "replace", 
      "path": "/a/b/c",
      "value": 123
    }
  ]
  </artwork></figure>
  
  <t>
    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.
  </t>

  <t>
    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 <xref target="RFC2119" />.
  </t> 

</section> 

<section anchor="predicates" title="Predicate Objects">

  <t>
    A JSON Predicate is a JSON Object whose members describe 
    a testable condition that evaluates as either
    true or false.
  </t>
  
  <t>
    The essential components of a JSON Predicate include:
    <list style="symbols">
      <t>
        A label identifying the predicate operation,
      </t>
      <t>
        A reference to the value being tested, and 
      </t>
      <t>
        The condition against which the referenced value 
        is to be evaluated.
      </t>
  </list>
  </t>

  <t>
    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.
  </t>  
  
  <t>
    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".
  </t>
  
  <t>
    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.
  </t>
  
  <t>
    The remaining structure of each predicate operation depends on the
    specific type. There are two basic types of predicates.
    <list style="symbols">
      <t>
        First Order Predicates that are used to test one name value pair 
        against a single condition, and
      </t>
      <t>
        Second Order Predicates that aggregate one or 
        more subordinate First or Second Order Predicates.
      </t>
  </list>
  </t>

  <t>
    In addition to the required "op" member, First Order Predicates 
    have exactly one "path" member whose value MUST be a string 
    containing a <xref target="RFC6901">JSON-Pointer</xref> 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.
  </t>
  
  <t>
    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.
  </t>
  
  <t>
    Additional members can be required depending on the specific 
    predicate operation. All other members not explicitly defined by
    this specification MUST be ignored.
  </t>
  
  <t>
    Note that the ordering of members in JSON objects is not significant;
    therefore the following operations are equivalent:
  </t>
  
  <figure><artwork>
{"op": "contains", "path": "/a/b/c", "value": "ABC"}
{"path": "/a/b/c", "op": "contains", "value": "ABC"}
{"value": "ABC", "path": "/a/b/c", "op": "contains"}
  </artwork></figure>
  
  <section anchor="context" title="Predicate Context">
    
    <t>
      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.
    </t>
      
    <t>
      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.
    </t>
    
  </section>
  
  <section anchor="first-order-predicates" title="First Order Predicates">
    
    <section anchor="contains" title=""contains" and "contains-"">
  
      <t>
        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.
      </t>
    
      <figure><preamble>For example, given the JSON document:</preamble>
      <artwork>
{
  "a": {
    "b": "This is a test"
  }
}
      </artwork></figure>
  
      <figure><preamble>The following predicate would evaluate as 
        "true":</preamble><artwork>
{
  "op": "contains",
  "path": "/a/b", 
  "value": " is a "
}
      </artwork></figure>
    
      <t>
        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".
      </t>
    
      <figure><preamble>For instance, the following will evaluate 
      as "true":</preamble><artwork>
{
  "op": "contains-",
  "path": "/a/b/", 
  "value": " Is A "
}
      </artwork></figure>

    </section>
  
    <section anchor="defined" title="defined">
      
      <t>The "defined" predicate evaluates
      as true if the referenced element exists within the 
      target context.</t>
      
      <figure><preamble>For example, given the JSON document:</preamble>
      <artwork>
{
"a": {
  "b": null
 }
}
      </artwork></figure>
      
      <figure><preamble>The following predicate would evaluate as
        "true" because the path "/a/b" 
        exist within the document despite being explicitly set to
        null:</preamble><artwork>
{
  "op": "defined",
  "path": "/a/b"
}
      </artwork></figure>
      
      <figure><preamble>The following predicate would 
        evaluate as "false" because
        the path "/a/c" does exist within the document.</preamble><artwork>
{
  "op": "defined",
  "path": "/a/c"
}
      </artwork></figure>
      
    </section>
  
    <section anchor="ends" title="ends">
    
      <t>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.</t>
    
      <figure><preamble>For example, given the JSON document:</preamble>
      <artwork>
{
  "a": {
    "b": "This is a test"
  }
}
      </artwork></figure>
  
      <figure><preamble>The following predicate would evaluate as 
        "true":</preamble><artwork>
{
  "op": "ends",
  "path": "/a/b", 
  "value": " test"
}
      </artwork></figure>
    
      <t>
        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".</t>

      <figure><preamble>For instance, the following will evaluate as 
        "true":</preamble><artwork>
{
  "op": "ends-",
  "path": "/a/b/", 
  "value": " TEST"
}
      </artwork></figure> 
    
    </section>
      
    <section anchor="in" title="in">
      
      <t>The "in" predicate evaluates as true if the referenced element 
        specifies a value exactly equal to one of the members of a JSON
        array provided by the predicate's "value" member. Equality is 
        determined following the sames rules specified for the JSON 
        Patch "test" operation in <xref target="RFC6902"/>, Section 4.6,
        with one exception given for optional case-insensitive comparisons.</t>
      
        <figure><preamble>For example, given the JSON document:</preamble>
        <artwork>
  {
    "a": {
      "b": 10
    }
  }
        </artwork></figure>
    
        <figure><preamble>The following will evaluate as 
          "true":</preamble><artwork>
  {
    "op": "in",
    "path": "/a/b", 
    "value": [1, "foo", 10, {"z":"y"}]
  }
        </artwork></figure>
        
        <t>The value specified for the "value" member MUST be a JSON Array.</t>
        
        <t>
          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".
        </t>
      
    </section>
      
    <section anchor="less" title="less">
    
      <t>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.</t>
    
      <figure><preamble>For example, given the JSON document:</preamble>
      <artwork>
{
  "a": {
    "b": 10
  }
}
      </artwork></figure>
    
      <figure><preamble>The following will evaluate as 
        "true":</preamble><artwork>
{
  "op": "less",
  "path": "/a/b", 
  "value": 15
}
      </artwork></figure>
    
    </section>

    <section anchor="matches" title="matches">
    
      <t>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.</t>
    
      <figure><preamble>For example, given the JSON document:</preamble>
      <artwork>
{
  "a": {
    "b": "this is a test"
  }
}
      </artwork></figure>
    
      <figure><preamble>The following evalutes as 
        "true":</preamble><artwork>
{
  "op": "matches",
  "path": "/a/b", 
  "value": "[\\w\\s]*"
}
      </artwork></figure>
    
      <t>The predicate's matching pattern is expressed as a string 
      value conforming to the JavaScript Regular Expression syntax (see 
      <eref target="http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf"/>).</t> 
    
      <t>
        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").
      </t>
    
      <figure><preamble>For instance:</preamble><artwork>
{
  "op": "matches-", 
  "path": "/a/b/", 
  "value": "[\\w\\s]*"
}
      </artwork></figure> 
    
    </section>
    
    <section anchor="more" title="more">
    
      <t>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.</t>
    
      <figure><preamble>For example, given the JSON document:</preamble>
      <artwork>
{
  "a": {
    "b": 10
  }
}
      </artwork></figure>
    
      <figure><preamble>The following will evaluate as 
        "true":</preamble><artwork>
{
  "op": "more",
  "path": "/a/b", 
  "value": 5
}
      </artwork></figure>
    
    </section>

    <section anchor="starts" title="starts">
    
      <t>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.</t>
    
      <figure><preamble>For example, given the JSON document:</preamble>
      <artwork>
{
  "a": {
    "b": "This is a test"
  }
}
      </artwork></figure>
  
      <figure><preamble>The following predicate would evaluate as 
        "true":</preamble><artwork>
{
  "op": "starts",
  "path": "/a/b", 
  "value": "This "
}
      </artwork></figure>
    
      <t>
        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".
      </t>
    
      <figure><preamble>For instance, the following will evaluate as "true":</preamble><artwork>
{
  "op": "starts-",
  "path": "/a/b/", 
  "value": "this "
}
      </artwork></figure> 
    
    </section>
    
    <section anchor="test" title="test">
      
      <t>The JSON Patch "test" operation, as defined by <xref target="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 <xref target="RFC6902"/>, Section 4.6,
      with one exception given for optional case-insensitive comparisons.</t>
      
      <figure><preamble>For example, given the JSON document:</preamble><artwork>
{
  "a": {
    "b": "this is a test"
  }
}
      </artwork></figure>
      
      <figure><preamble>The following predicate would evaluate as 
        "true"</preamble><artwork>
{
  "op": "test",
  "path": "/a/b", 
  "value": "this is a test"
}
      </artwork></figure>
      
      <t>
        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".
      </t>
      
    </section>
    
    <section anchor="type" title="type">
    
      <t>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.</t>
    
      <t>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".</t>
    
      <figure><preamble>For example, given the JSON document:</preamble><artwork>
{
  "a": {
    "b": "this is a test",
    "c": [1,2,3]
  }
}
      </artwork></figure>
    
      <figure><preamble>The following predicate would evaluate as 
        "true"</preamble><artwork>
{
  "op": "type",
  "path": "/a/b", 
  "value": "string"
}
      </artwork></figure>
      
      <t>When evaluating the type of a value, the following rules apply:
        <list style="symbols">
          <t>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.</t>
          <t>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.</t>
          <t>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.</t>
          <t>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.</t>
          <t>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.</t>
          <t>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.</t>
          <t>If the "value" member specifies "undefined", the type predicate
            will evaluate as true if the member referenced by the "path"
            member does not exist.</t>
          <t>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 <xref target="RFC3339"/>
            "full-date" construct.</t>
          <t>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 <xref target="RFC3339"/>
            "full-time" construct.</t>
          <t>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 <xref target="RFC3339"/>
            "date-time" construct.</t>
          <t>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 <xref target="RFC5646"/>
            "Language-Tag" construct.</t>
          <t>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 <xref target="RFC4647"/>
            "language-range" construct.</t>
          <t>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 <xref target="RFC3987"/>
            "IRI-reference" construct.</t>
          <t>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 <xref target="RFC3987"/>
            "IRI" construct.</t>
        </list>
      </t>
    </section>
    
    <section anchor="undefined" title="undefined">
      
      <t>The "undefined" predicate evaluates
      as true if the referenced element does not exist within the 
      target context.</t>
      
      <figure><preamble>For example, given the JSON document:</preamble>
      <artwork>
{
"a": {
  "b": null
 }
}
      </artwork></figure>
      
      <figure><preamble>The following predicate would evaluate as
        "true" because the path "/a/c" 
        does not exist within the document:</preamble><artwork>
{
  "op": "undefined",
  "path": "/a/c"
}
      </artwork></figure>
      
      <figure><preamble>However, the following predicate would 
        evaluate as "false" because
        the path "/a/b" does exist within the document, despite
        specifying an explicit null value.</preamble><artwork>
{
  "op": "undefined",
  "path": "/a/b"
}
      </artwork></figure>
      
    </section>

    
  </section>

  <section anchor="second-order-predicates" title="Second-Order Predicates">
        
    <t>Second Order Predicates are defined as sets of one or more
    subordinate First and Second Order Predicates.</t>
    
    <t>All Second Order Predicates MAY contain a "path" member whose 
    value specifies a root path prefix for all contained predicates.
    If the "path" member is not specified, it's value is assumed to 
    be an empty string. For example, given the JSON document:</t>
    
    <figure><artwork>
{
  "a": {
    "b": {
      "c": "ABC!"
    }
  }
}
    </artwork></figure>
    
    <figure><preamble>The following would evaluate as true because the path
    "/a/b/c" is defined.</preamble><artwork>
{
  "op": "and",
  "path": "/a/b",
  "apply": [
    {
      "op": "defined", 
      "path": "/c"
    }
  ]
}
    </artwork></figure>
    
    <figure><preamble>The above example is equivalent to:</preamble><artwork>
    {
      "op": "and",
      "apply": [
        {
          "op": "defined", 
          "path": "/a/b/c"
        }
      ]
    }
    </artwork></figure>

    <section anchor="and" title="and">
    
      <t>The "and" predicate evaluates as 
      "true" if all of it's contained set 
      of predicate operations evaluate as "true".</t>

      <figure><preamble>For example, given the JSON document:</preamble><artwork>
{
  "a" : {
    "b" : "foo",
    "c" : {
      "d": 10
    }
  }
}
      </artwork></figure>
    
      <figure><preamble>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.</preamble><artwork>
{
  "op": "and",
  "apply" [
    {
      "op": "defined", 
      "path": "/a/b"
    },
    {
      "op": "less",
      "path": "/a/c/d", 
      "value": 15
    }
  ]
}
      </artwork></figure>
    
      <figure><preamble>However, the following would evaluate as 
      "false" because while the element "/a/c" exists, the value 
      of that element is not a string.</preamble><artwork>
{
  "op": "and",
  "apply": [
    {
      "op": "test",
      "path": "/a/c"
    },
    {
      "op": "type",
      "path": "/a/c", 
      "value": "string"
    }
  ]
}
      </artwork></figure>
    
    </section>

    <section anchor="not" title="not">
    
      <t>The "not" predicate evaluates as 
      "true" if all of it's contained set of predicate 
      operations evaluate as "false".</t>
    
      <figure><preamble>For example, given the JSON document:</preamble><artwork>
{
  "a" : {
    "b" : "foo",
    "c" : {
      "d": 10
    }
  }
}
      </artwork></figure>
    
      <figure><preamble>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.</preamble><artwork>
{
  "op": "not",
  "apply": [
    {
      "op": "defined", 
      "path": "/a/b/e"
    },
    {
      "op": "less", 
      "path": "/a/c/d", 
      "value": 5
    }
  ]
}
      </artwork></figure>
    
      <figure><preamble>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"</preamble><artwork>
{
  "op": "not",
  "apply": [
    {
      "op": "undefined", 
      "path": "/a/c"
    },
    {
      "op": "starts", 
      "path": "/a/b", 
      "value": "f"
    }
  ]
}
      </artwork></figure>    
    </section>
  
    <section anchor="or" title="or">

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

      <figure><preamble>For example, given the JSON 
        document:</preamble><artwork>
{
  "a" : {
    "b" : "foo",
    "c" : {
      "d": 10
    }
  }
}
      </artwork></figure>
    
      <figure><preamble>The following would evaluate as "true" 
      because the element "/a/b" is defined.</preamble><artwork>
{
  "op": "or",
  "apply": [
    {
      "op": "defined",
      "path": "/a/b"
    },
    {
      "op": "less",
      "path": "/a/c/d", 
      "value": 5
    }
  ]
}
      </artwork></figure>
    
      <figure><preamble>However, the following would evaluate as 
        "false" because neither elements "/a/e" or "/a/f" exist.</preamble><artwork>
{
  "op": "or",
  "apply": [
    {
      "op": "test",
      "path": "/a/e"
    },
    {
      "op": "test",
      "path": "/a/f"
    }
  ]
}
      </artwork></figure>   

    </section>
  
    <section title="Nesting Second Order Predicates">
    
      <t>Second Order Predicates can be combined in a variety 
      of ways to define more complex test operations. For example:</t>
    
      <figure><artwork>
{
  "op": "or",
  "path": "/a/b",
  "apply": [
    {
      "op": "not",
      "path": "/c",
      "apply": [
        {"op": "undefined"},
        {"op": "starts", "value": "f"}
      ]
    },
    {
      "op": "not",
      "path": "/d",
      "apply": [
        {"op": "defined"},
        {"op": "type", "value": "number"}
      ]
    }
  ]
}
      </artwork></figure>
    
    </section>
  
  </section>
      
  <section anchor="error-handling" title="Error Handling">
    
    <t>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.</t>
    
    <t>Error conditions can arise in each of the following conditions:
      <list style="symbols">
        <t>
          JSON Predicate Objects contained within a document fail to 
          conform to any normative requirement of this specification, or
        </t>
        <t>
          The Predicate Object specifies an unknown predicate operation, or
        </t>
        <t>
          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
        </t>
        <t>
          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.
        </t>
        <t>
          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).
        </t>
      </list>
    </t>
    
  </section>
  
  <section anchor="json-patch" title="Using JSON Predicates within JSON Patch Documents">
    
    <t>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.</t>
    
    <t>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.</t>
    
    <figure><preamble>For example, given the following JSON document:</preamble>
    <artwork>
{
  "a": {
    "b": {
      "c": "123"
    }
  }
}
    </artwork></figure>
    
    <figure><preamble>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:</preamble><artwork>
[
  {
    "op": "and",
    "path": "/a/b/c",
    "apply": [
      {"op": "type", "value": "string"},
      {"op": "matches", "value": "\\d{3}"}
    ]
  },
  {
    "op": "replace",
    "path": "/a/b/c",
    "value": "ABC"
  }
]
    </artwork></figure>
    
    <t>When a JSON Predicate object within a JSON Patch document evaluates as 
    false, processing of the JSON Patch Document MUST be handled exactly the 
    same as an unsuccessful JSON Patch operation would be handled as defined 
    in <xref target="RFC6902">JSON-PATCH</xref>. Specifically, processing of 
    the JSON Patch document SHOULD terminate and application of the entire 
    patch document SHALL NOT be deemed successful.</t>
    
    <t>The MIME media type "application/json-patch-test" is used to identify 
    JSON Patch documents that contain predicates.</t>
  
    <t>For example:</t>
    
    <figure><artwork>
  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"
    }
  ] 
    </artwork></figure>
    
    <t>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.</t>
    
    <section title="Conditional Patch Operations">
      
      <t>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.</t>
        
      <t>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.</t>
      
      <figure><artwork>
  PATCH /some-document HTTP/1.1
  Host: example.org
  Content-Type: application/json-patch-test+json
  
  [
    {
      "op": "remove",
      "path": "/a/b/0",
      "if": {
        "op": "type",
        "path": "/a/b",
        "value": "array"
      }
    }
  ]
      </artwork></figure>
      
      <t>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.</t>
      
      <t>In the following example, the value at path "/a/b/0" is 
      removed unless the value at path "/a/b" is undefined and 
      missing.</t>
      
      <figure><artwork>
  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"
      }
    }
  ]
      </artwork></figure>
      
      <t>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.</t>
      
      <figure><artwork>
  PATCH /some-document HTTP/1.1
  Host: example.org
  Content-Type: application/json-patch-test
  
  [
    {
      "op": "add",
      "path": "/a/b",
      "value": [],
      "unless": {
        "op": "and",
        "apply": [
          {"op": "defined"},
          {"op": "type", "value": "array"}
        ]
      }
    },
    {
      "op": "add",
      "path": "/a/b/-",
      "value": "ABC"
    }
  ]
      </artwork></figure>
      
      <t>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.</t>
      
    </section>
    
  </section>

</section>
      
<section title="IANA Considerations">
  
   <t>The Internet media type for a JSON Patch document containing JSON Predicate 
   objects is application/json-patch-test+json.</t>

   <figure><artwork>
   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]

   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
   </artwork></figure>    
   
</section>
      
<section title="Security Considerations">
<t>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.</t>
</section>

</middle> 
<back>
<references title="Normative References"> 
  &rfc2119;
  &rfc3987;
  &rfc4647;
  &rfc5646;
  &rfc4627;
  &rfc3339;
  &rfc6901;
</references>
<references title="Informative References">
  &rfc6902;
</references>
<section title="Non-Normative Example Implementation (Ruby)">
  
  <t>
    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.
  </t>
  
  <figure><artwork><![CDATA[
####################################
# 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

  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
    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
      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] == '-'
        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

      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

    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

    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']
        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| 
        other.register_op x, ->(params,target) {
        raise Patch::FailedOperationError unless y.call params,target
        }
      }
  
    end
  end

end # End Module
  ]]></artwork></figure>
  
  <t>
    The following illustrates how the Ruby module is used:
  </t>
  
  <figure><artwork><![CDATA[
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
  ]]></artwork></figure>
  
</section>
</back>
</rfc> 
 

PAFTECH AB 2003-20262026-04-24 05:23:32