Network Working Group                                          J. Bognar
Request for Comments: 9999                                                
Category: Informational                                       Salesforce
                                                                Feb 2017

                            ***DRAFT***
               URI Object Notation (UON): Generic Syntax


About this document

   This memo provides information for the Internet community.  It does
   not specify an Internet standard of any kind.  Distribution of this
   memo is unlimited.

Copyright Notice

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you under the Apache License, Version 2.0 (the
  "License"); you may not use this file except in compliance
  with the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing,
  software distributed under the License is distributed on an
  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  KIND, either express or implied.  See the License for the
  specific language governing permissions and limitations
  under the License.

Abstract

   This document describes a grammar that builds upon RFC2396
   (Uniform Resource Identifiers).  Its purpose is to define a 
   generalized object notation for URI query parameter values similar in 
   concept to Javascript Object Notation (RFC4627).  The goal is a 
   syntax that allows for array and map structures to be
   such that any data structure defined in JSON can be losslessly 
   defined in an equivalent URI-based grammar, yet be fully compliant 
   with the RFC2396 specification. 

   This grammar provides the ability to construct the following data 
   structures in URL parameter values: 
	
      OBJECT
      ARRAY
      NUMBER
      BOOLEAN
      STRING
      NULL

   Example:

      The following shows a sample object defined in Javascript:

         var x = { 
            id: 1, 
            name: 'John Smith', 
            uri: 'http://sample/addressBook/person/1', 
            addressBookUri: 'http://sample/addressBook', 
            birthDate: '1946-08-12T00:00:00Z', 
            otherIds: null,
            addresses: [ 
               { 
                  uri: 'http://sample/addressBook/address/1', 
                  personUri: 'http://sample/addressBook/person/1', 
                  id: 1, 
                  street: '100 Main Street', 
                  city: 'Anywhereville', 
                  state: 'NY', 
                  zip: 12345, 
                  isCurrent: true,
               } 
            ] 
         } 

      Using the syntax defined in this document, the equivalent 
      UON notation would be as follows:

         x=(id=1,name='John+Smith',uri=http://sample/
         addressBook/person/1,addressBookUri=http://sample/
         addressBook,birthDate=1946-08-12T00:00:00Z,otherIds=null,
         addresses=@((uri=http://sample/addressBook/
         address/1,personUri=http://sample/addressBook/
         person/1,id=1,street='100+Main+Street',city=
         Anywhereville,state=NY,zip=12345,isCurrent=true))) 

1. Language constraints

   The grammar syntax is constrained to usage of characters allowed by 
      URI notation:

      uric       = reserved | unreserved | escaped
      reserved   = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
                   "$" | ","
      unreserved = alphanum | mark
      mark       = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"

   In particular, the URI specification disallows the following 
   characters in unencoded form:

      unwise     = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
      delims     = "<" | ">" | "#" | "%" | <">

   The exclusion of {} and [] characters eliminates the possibility of 
   using JSON as parameter values.


2. Grammar constructs

   The grammar consists of the following language constructs:
   
      Objects - Values consisting of one or more child name/value pairs.
      Arrays - Values consisting of zero or more child values.
      Booleans - Values consisting of true/false values.
      Numbers - Decimal and floating point values.
      Strings - Everything else.

2.1. Objects 

   Objects are values consisting of one or more child name/value pairs.
   The (...) construct is used to define an object.

   Example:  A simple map with two key/value pairs:

      a1=(b1=x1,b2=x2)

   Example:  A nested map:
   
      a1=(b1=(c1=x1,c2=x2))

2.2. Arrays

   Arrays are values consisting of zero or more child values.
   The @(...) construct is used to define an array.

   Example:  An array of two string values:

      a1=@(x1,x2)

   Example:  A 2-dimensional array:

      a1=@(@(x1,x2),@(x3,x4))

   Example:  An array of objects:

      a1=@((b1=x1,b2=x2),(c1=x1,c2=x2))

2.3. Booleans

   Booleans are values that can only take on values "true" or "false".  
   
   Example:  Two boolean values:

      a1=true&a2=false
   
2.4. Numbers

   Numbers are represented without constructs.
   Both decimal and float numbers are supported.
   
   Example:  Two numerical values, one decimal and one float:

      a1=123&a2=1.23e1

2.5. Null values

   Nulls are represented by the keyword 'null':

      a1=null

2.6. Strings

   Strings are encapsulated in single quote (') characters.
   
   Example:  Simple string values:

      a1='foobar'&a2='123'&a3='true'
         
   The quotes are optional when the string cannot be confused
   with one of the constructs listed above (i.e. objects/arrays/
   booleans/numbers/null). 
   
   Example:  A simple string value, unquoted:

      a1=foobar
    
   Strings must be quoted for the following cases:
      
      - The string can be confused with a boolean or number.
      - The string is empty.
      - The string contains one or more whitespace characters.
      - The string starts with one of the following characters:
        @ (
      - The string contains any of the following characters:
        ) , =
   
   For example, the string literal "(b1=x)" should be 
   represented as follows:

      a1='(b1=x)'
   
   The tilde character (~) is used for escaping characters to prevent 
   them from being confused with syntax characters.  

   The following characters must be escaped in string literals:  

      ' ~
   
   Example:  The string "foo'bar~baz"

      a1='foo~'bar~~baz'
   
2.7. Top-level attribute names

   Top-level attribute names (e.g. "a1" in "&a1=foobar") are treated
   as strings but for one exception.  The '=' character must be
   encoded so as not to be confused as a key/value separator.
   Note that the '=' character must also be escaped per the UON
   notation.
   
   For example, the UON equivalent of {"a=b":"a=b"} constructed as
   a top-level query parameter string would be as follows:
   
      a~%3Db=a~=b
      
   Note that the '=' character is encoded in the attribute name,
   but it is not necessary to have it encoded in the attribute value.

2.8. URL-encoded characters

   UON notation allows for any character, even UON grammar
   characters, to be URL-encoded.
   
   The following query strings are fully equivalent in structure:
   
     a1=(b1='x1',b2='x2')
     %61%31=%79%6f%75%20%61%72%65%20%61%20%6e%65%72%64%21


3. BNF

   The following BNF describes the syntax for top-level URI query 
   parameter values (e.g. ?<attrname>=<value>).

   attrname    = (string | null)
   value       = (var | string | null)

   string      = ("'" litchar* "'") | litchar*
   null        = "null"
   
   var         = ovar | avar | nvar | boolean | number
   ovar        = "(" [pairs] ")"
   avar        = "@(" [values] ")"

   pairs       = pair ["," pairs]
   pair        = key "=" value	
   values      = value ["," values]
   key         = (string | null)
   boolean     = "true" | "false"

   escape_seq  = "~" escaped
   encode_seq  = "%" digithex digithex

   number      = [-] (decimal | float) [exp]
   decimal     = "0" | (digit19 digit*)
   float       = decimal "." digit+
   exp         = "e" [("+" | "-")] digit+

   litchar     = unencoded | encode_seq | escape_seq
   escaped     = "@" | "," | "(" | ")" | "~" | "=" 
   unencoded   = alpha | digit | 
                 ";" | "/" | "?" | ":" | "@" |
                 "-" | "_" | "." | "!" | "*" | "'" 
   alpha       = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" |
                 "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" |
                 "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" |
                 "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" |             
                 "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" |
                 "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"
   digit       = "0" | digit19
   digit19     = "1" | "2" | "3" | "4" | "5" | "6" | "7" |
                 "8" | "9"
   digithex    = digit | 
                 "A" | "B" | "C" | "D" | "E" | "F" |
                 "a" | "b" | "c" | "d" | "e" | "f"




