Individual Submission J.M. Snell
Internet-Draft December 05, 2012
Intended status: Informational
Expires: June 08, 2013

JSON Predicate
draft-snell-json-test-03

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 June 08, 2013.

Copyright Notice

Copyright (c) 2012 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.


Table of Contents

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, as a means of incorporating conditional handling during the processing of a JSON document.

JSON Predicates can be used, for instance, to extend a JSON Patch [I-D.ietf-appsawg-json-patch] document to provide for a broader range of conditional processing options not currently supported by JSON Patch.

Example: Given a source JSON document

  {
    "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",
      "apply": [
        {
          "op": "type", 
          "path": "/c", 
          "value": "string"
        },
        {
          "op": "contains", 
          "path": "/c", 
          "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, like JSON Patch.

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

A JSON Predicate is a JSON Object whose name value pairs describe testable conditions that evaluate as either true or false.

The essential components of a JSON Predicate include:

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", "defined", "ends", "in", "less", "matches", "more", "not", "or", "starts", "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 treated as if a boolean value of "false" was returned. The application processing the predicate operation might 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 it's specific type. There are two basic types of 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 value referencing the location of 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.

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

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":

{
  "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, the predicate object MAY contain an "ignore_case" member whose value is either true (to perform case-insensitive matching) or false.

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

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

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.

{
  "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, the predicate object MAY contain an "ignore_case" member whose value is either "true" (to perform case-insensitive matching) or false.

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

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

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 provided by the predicate's "value" member. Equality is determined following the sames rules specified for the JSON Patch "test" operation in [I-D.ietf-appsawg-json-patch], 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, the predicate object MAY contain an "ignore_case" member whose value is either "true" (to perform case-insensitive matching) or false.

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":

{
  "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.

By default, character matching MUST be performed in a case-sensitive manner. To override this default behavior, the predicate object MAY contain an "ignore_case" member whose value is either "true" (to perform case-insensitive matching) or false. Setting the value of "ignore_case" to true is equivalent to using the "i" modifier flag within the JavaScript Regular Expression syntax (e.g. "/\w\s/*/i").

For instance:

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

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"
  }
}
      

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, the predicate object MAY contain an "ignore_case" member whose value is either "true" (to perform case-insensitive matching) or false.

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

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

2.2.9. test

The JSON Patch "test" operation, as defined by [I-D.ietf-appsawg-json-patch], 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 [I-D.ietf-appsawg-json-patch], Section 4.6, with one exception given for optional case-insensitive comparisons.

For example, given the JSON document:

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

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, the predicate object MAY contain an "ignore_case" member whose value is either "true" (to perform case-insensitive matching) or "false".

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:

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 "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:

{
  "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"
    }
  ]
}
      

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"

{
  "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.

{
  "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"},
        {"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:

2.5. Using JSON Predicate 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 exactly the same as an unsuccessful JSON Patch operation would be handled as defined in JSON-PATCH [I-D.ietf-appsawg-json-patch], Section 5. 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
  
  [
    {
      "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.

3. IANA Considerations

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

   Type name:  application

   Subtype name:  json-patch-test

   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
   

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.
[RFC3987] Duerst, M. and M. Suignard, "Internationalized Resource Identifiers (IRIs)", RFC 3987, January 2005.
[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.
[RFC4627] Crockford, D., "The application/json Media Type for JavaScript Object Notation (JSON)", RFC 4627, July 2006.
[RFC3339] Klyne, G. and C. Newman, "Date and Time on the Internet: Timestamps", RFC 3339, July 2002.

5.2. Informative References

[I-D.ietf-appsawg-json-patch] Bryan, P and M Nottingham, "JSON Patch", Internet-Draft draft-ietf-appsawg-json-patch-07, December 2012.

Author's Address

James M Snell EMail: jasnell@gmail.com