Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

JSON version 4 to C# Objects and Back -- Part 2

0.00/5 (No votes)
3 Nov 2016 2  
A set of C# classes to create and manipulate JSON Schema and Instances.

Introduction

It is assumed that the reader has read and understood the first part of this two-part article. If this is the case please proceed with the rest of this work.

 

Table of Contents

Using the code

As with Part 1, the JSON concepts and how they are implemented are described using code fragments. Some of the examples used here are based on the work of Michael Droettboom and others of the Space Telescope Science Institute.

JSON References 

//Example_2_1

 /*   
{
      "$schema": "http://json-schema.org/draft-04/schema#",

      "id": "ExampleID-021",

      "title": "refExamples",

      "description": "REF Demo Schema",

      "type": "object",

      "properties":  {

                       "integerOne": {
                       "type": "integer",
                       "minimum": 200,
                       "maximum": 500,
                       "exclusiveMinimum": true,
                       "exclusiveMaximum": true,
                       "multipleOf": 20
                        },

                        "integerTwo": {
                        "type": "integer",
                        "minimum": 200,
                        "maximum": 500,
                        "exclusiveMinimum": true,
                        "exclusiveMaximum": true,
                        "multipleOf": 20
                        },

                        "integerThree": {
                        "type": "integer",
                        "minimum": 200,
                        "maximum": 500,
                        "exclusiveMinimum": true,
                        "exclusiveMaximum": true,
                        "multipleOf": 20
                         }
            },
       "required": ["integerOne", "integerTwo", "integerThree"]
}
*/

Consider the JSON schema text above. Observe that the three properties of the top level schema i.e., integerOne, integerTwo and integerThree all have the same parameters. Regardless, each of these properties had to be individually created thus violating the DRY principle. The JSON schema specification attempts to mitigate this definition duplication using three mechanisms. These mechanisms allow object parameters to be defined outside an object and these defined parameters are then referenced within the objects.   At run-time, the references are replaced by the parameters being referenced. 

For this work the three referencing techniques are called Internal, Definitions and External.

Internal Referencing

//Example_2_2
        
/*
 {
     "$schema": "http://json-schema.org/draft-04/schema#",

     "id": "ExampleID-022",

     "title": "refExamples",

     "description": "REF Demo Schema",

     "type": "object",

     "properties":  {

                      "integerOne": {
                       "id": "integer_id_001",
                       "type": "integer",
                       "minimum": 200,
                       "maximum": 500,
                       "exclusiveMinimum": true,
                       "exclusiveMaximum": true,
                       "multipleOf": 20
                       },

                        "integerTwo": {
                        "$ref": "#integer_id_001"
                        },

                       "integerThree": {
                       "$ref": "#integer_id_001"
                        }
                     },
     "required": ["integerOne", "integerTwo", "integerThree"]

}
        
 */

Above is an example of a schema implementing Internal referencing. As can be seen, the parameters are defined once in the integerOne property and then a reference using the $ref keyword with the identity of IntegerOne is placed inside the other two properties.  Functionally, the schema text above is similar to the schema text in the Example_2_1 routine. To check if this is true, deserialize the JSON text above and then serialize it again. Print out the serialized text to confirm that this is the case.

The C# code below is used to produce the schema text shown above.

//Example_2_2
    
JsonSchemaObject _json_schema_object = new JsonSchemaObject (JsonUtilities.JSON_VERSION.V4, 
"refExamples", "REF Demo Schema", "ExampleID-022");


JsonSchemaObject _json_schema_integerOne = new JsonSchemaObject ("integerOne", JSON_TYPE.INTEGER,200, 500, 20, string.Empty, true);

_json_schema_integerOne.ExclusiveMinimum = true;
_json_schema_integerOne.ExclusiveMaximum = true;
_json_schema_integerOne.ObjectID = "integer_id_001";


JsonSchemaObject _json_schema_integerTwo   = new JsonSchemaObject("integerTwo",
JSON_POINTER_TYPE.INTERNAL,_json_schema_integerOne.ObjectID,string.Empty,true);

JsonSchemaObject _json_schema_integerThree = new JsonSchemaObject("integerThree",
JSON_POINTER_TYPE.INTERNAL,_json_schema_integerOne.ObjectID,string.Empty,true);


_json_schema_object.AddObjects (new List<JsonSchemaObject> (new JsonSchemaObject [] {
				_json_schema_integerOne,
				_json_schema_integerTwo,
				_json_schema_integerThree})
				);



_schema_string = _json_schema_engine.Serialize (_json_schema_object);

Notice that integerTwo and IntegerThree are instantiated using the reference constructor i.e., JsonSchemaObject (string object_name,JSON_POINTER_TYPE ref_type,string json_schema_reference_uri,string description,bool is_required = false).  Along with the object name, this constructor is passed the type of reference, in the case JSON_POINTER_TYPE.INTERNAL, and the identity of the object being referenced i.e., _json_schema_integerOne.ObjectID

An exception is thrown during the deserialization process if the object referenced cannot be found.

The reference type and its associated URI can be read from the SchemaReferenceType and SchemaReferenceUri properties of the JsonSchemaObject class.

Definitions Referencing

//Example_2_3

/* { 

    "$schema": "http://json-schema.org/draft-04/schema#", 

    "id": "ExampleID-023",
 
    "title": "refExamples", 

    "description": "REF Demo Schema", 

    "type": "object",
 
     "properties": { 
                   "integerOne":  { 
                                "$ref": "#/definitions/integerDef" 
                                },

                   "integerTwo": { 
                               "$ref": "#/definitions/integerDef" 
                               }, 

                  "integerThree": { 
                              "$ref": "#/definitions/integerDef" 
                              } 
                  }, 
    "required": ["integerOne", "integerTwo", "integerThree"], 

     definitions: { 

                "integerDef": { 
                              "type": "integer", 
                              "minimum": 200,
                              "maximum": 500, 
                              "exclusiveMinimum": true,
                              "exclusiveMaximum": true, 
                              "multipleOf": 20 
                            } 
                 } 

} 

*/ 

In this case, defined schema parameters are placed inside special object called definitions. The reference to parameters is placed inside each of the relevant objects as shown above for the IntegerOne, integerTwo and integerThree properties. 

The code used to accomplish the above is as shown below.

//Example_2_3
  
JsonSchemaObject _json_schema_object = new JsonSchemaObject (JsonUtilities.JSON_VERSION.V4, 
"refExamples","REF Demo Schema", "ExampleID-023");

JsonSchemaObject _json_schema_integerDef = new JsonSchemaObject ("integerDef",
 JSON_TYPE.INTEGER,200, 500, 20, string.Empty, true);

_json_schema_integerDef.ExclusiveMinimum = true;
_json_schema_integerDef.ExclusiveMaximum = true;


_json_schema_object.AddDefinition (_json_schema_integerDef);



JsonSchemaObject _json_schema_integerOne = new JsonSchemaObject ("integerOne",
JSON_POINTER_TYPE.DEFINITIONS,_json_schema_integerDef.ObjectName, string.Empty, true);


JsonSchemaObject _json_schema_integerTwo = new JsonSchemaObject ("integerTwo",
JSON_POINTER_TYPE.DEFINITIONS, _json_schema_integerDef.ObjectName, string.Empty, true);



JsonSchemaObject _json_schema_integerThree = new JsonSchemaObject ("integerThree",
JSON_POINTER_TYPE.DEFINITIONS, _json_schema_integerDef.ObjectName, string.Empty, true);


		
_json_schema_object.AddObjects (new List<jsonschemaobject> (new JsonSchemaObject [] {
				_json_schema_integerOne,
				_json_schema_integerTwo,
				_json_schema_integerThree})
				);
	
			
				
_schema_string = _json_schema_engine.Serialize (_json_schema_object);  

Again, the JsonSchemaObject (string object_name,JSON_POINTER_TYPE ref_type,string json_schema_reference_uri,string description,bool is_required = false) constructor is used but this time it is passed the JSON_POINTER_TYPE.DEFINITIONS as a parameter. Also, rather than passing the object ID as was done for Internal referencing, the object name i.e., integerDef, is used instead.

Another example of a definitions URI is "$ref": "#/definitions/jsonObject/integerDef" . This URI says that the integerDef is inside a JSON Object type called jsonObject and jsonObject, in turn, is inside of the definitions object.

An exception is thrown during the deserialization process if the object referenced cannot be found in the all definitions objects within the scope or the parent scope of the object that contains the $ref attribute.

The reference type and its associated URI can be read from the SchemaReferenceType and SchemaReferenceUri properties of the JsonSchemaObject class.

External Referencing

In the Internal and Definitions referencing mechanism discussed so far, the schema being referenced is contained locally within the same object as the referred object. JSON allows for the referencing of a schema that resides outside an object, hence the term External referencing

//Example_2_4

/*
{
        "$schema": "http://json-schema.org/draft-04/schema#",

        "id": "http://x.y.z/rootschema.json#",

        "title": "refExamples",

        "description": "External Schema",

        "type": "object",

         "properties":  {
                         "integerExternal": {
                         "id": "integer_id_024",
                         "type": "integer",
                         "minimum": 200,
                         "maximum": 500,
                         "exclusiveMinimum": true,
                         "exclusiveMaximum": true,
                         "multipleOf": 20
                        }
}

*/

A typical external schema will look similar in structure to the JSON text above. Note that the identity of the top level object is a link i.e., "http://x.y.z/rootschema.json#".  Referring to the integerExternal from outside is done using this link and the identity of integerExternal as shown below. 

//Example_2_4

/*
{
     "$schema": "http://json-schema.org/draft-04/schema#",

     "id": "ExampleID-024",

     "title": "refExamples",

     "description": "REF Demo Schema",

     "type": "object",

     "properties":  {

                     "integerOne": {
                         "$ref": "http://x.y.z/rootschema.json#integer_id_024"
                      },

                      "integerTwo": {
                          "$ref": "http://x.y.z/rootschema.json#integer_id_024"
                       },

                       "integerThree": {
                           "$ref": "http://x.y.z/rootschema.json#integer_id_024"
                        }


     },

     "required": ["integerOne", "integerTwo", "integerThree"]

}
*/

The code used to accomplish the above is contained in the Example_2_4 routine. See below for excerpts.

/Example_2_4
    
string _object_id = "integer_id_024";
string _ref_schema_id = "http://x.y.z/rootschema.json#";


JsonSchemaObject _json_schema_object = new JsonSchemaObject (JsonUtilities.JSON_VERSION.V4, 
"refExamples","REF Demo Schema", "ExampleID-024");

JsonSchemaObject _json_schema_integerOne = new JsonSchemaObject ("integerOne",
JSON_POINTER_TYPE.EXTERNAL, _ref_schema_id + _object_id, string.Empty, true);

JsonSchemaObject _json_schema_integerTwo = new JsonSchemaObject ("integerTwo",
JSON_POINTER_TYPE.EXTERNAL, _ref_schema_id + _object_id, string.Empty, true);

JsonSchemaObject _json_schema_integerThree = new JsonSchemaObject ("integerThree",
JSON_POINTER_TYPE.EXTERNAL, _ref_schema_id + _object_id, string.Empty, true);


_json_schema_object.AddObjects (
new List<JsonSchemaObject> (new JsonSchemaObject [] {
_json_schema_integerOne,
_json_schema_integerTwo,
_json_schema_integerThree})
				);


_schema_string = _json_schema_engine.Serialize (_json_schema_object);



 
...
 
 _json_schema_engine.AddSchemaRef (_json_schema_external_object);

JsonSchemaObject _json_schema_object_deserialized = _json_schema_engine.Deserialize (
_schema_string);
  
...

If an object being referenced is within an external schema, it is the duty of the application to retrieve this external schema before the deserialization routine is called. Otherwise, an exception is thrown during deserialization as the schema will not be found.  Use the any of the four AddSchemaRef routine to add external schemas (or schemata) before calling the Deserialize routine. See the Example_2_4 routine on how this is implemented.

The reference type and its associated URI can be read from the SchemaReferenceType and SchemaReferenceUri properties of the JsonSchemaObject class.

JSON Array Type Attributes

In the first part of this work, the concept of JSON Array type was introduced. This section recaps some of the Array parameters discussed previously and introduces new ones.

 

Unique, Minimum and Maxumum Items

//Example_2_5

/*
{
      "$schema": "http://json-schema.org/draft-04/schema#",

      "id": "ExampleID-025",

      "title": "arrayExamples",

      "description": "Array Demo Schema",

      "type": "object",

      "properties":  {

                  "arrayObject": {
                                "type": "array",
                                 "items": 
                                       {
                                       "type": "integer"
                                       },
                                 "minItems": 1,
                                 "maxItems": 4,
                                 "uniqueItems": true
                 }
     },

    "required": ["arrayObject"]
}

*/

The schema text shown above should be familiar to the reader by now. It is a schema that describes a JSON Array type that consists of JSON Integer type items. An instance of this Array schema should contain no more than 4 integer items and no less than 1. All items in the Array must be unique.

As shown in the code extracts below, the schema for the array was created using the constructor JsonSchemaObject (string object_name,int? minimum_items,int? maximum_items,bool is_unique_items,bool additional_items,string description,bool is_required = false)

//Example_2_5

JsonSchemaObject _json_schema_integer = new JsonSchemaObject ("integerObject", 
JSON_TYPE.INTEGER,null, null, null, string.Empty, false);

JsonSchemaObject  _json_schema_array   = new JsonSchemaObject ("arrayObject",1,4,true,true,
string.Empty,true);

_json_schema_array.AddObject (_json_schema_integer);

JsonSchemaObject _json_schema_object = new JsonSchemaObject (JsonUtilities.JSON_VERSION.V4, 
"arrayExamples","Array Demo Schema", "ExampleID-025");

_json_schema_object.AddObject (_json_schema_array);

_schema_string = _json_schema_engine.Serialize (_json_schema_object);

For Example_2_5, an instance for the schema above is created using the code below.

//Example_2_4

_instance_object_string_list = new List<JsonInstanceObject> ();

int _array_size = 4;

for (int i=0; i < _array_size; i++) 
{
	_instance_object_list.Add(new JsonInstanceObject(_json_schema_integer,(i+1)));

}

/* Line 1:
 
_instance_object_list.Add(new JsonInstanceObject(_json_schema_integer,(1)));

//*/

JsonInstanceObject _json_instance_array = new JsonInstanceObject (_json_schema_array, 
_instance_object_list);

_instance_object_list.Clear ();

_instance_object_list.Add (_json_instance_array);

JsonInstanceObject _json_instance_object = new JsonInstanceObject (_json_schema_object, 
_instance_object_list);

_instance_string = _json_instance_engine.Serialize (_json_instance_object);

To violate the maximum and minimum number of items set for the Array change the value of the _array_size variable to a value less than 1 or greater than 4. What did the exception say?
To go against the uniqueItems constraint of the JSON Array, comment out Line 1 and run Example_2_5. Read the exception thrown.

The JSON Array parameters uniqueItems, minItems, maxItems can be read from the UniqueItems, MinimumItems, MaximumItems properties of the JsonSchemaObject  class.

Below is the serialized instance of the Array schema.

Example_2_5

/*
{
"arrayObject": [1, 2, 3, 4]
}
*/

 

Array Tuple

Counterintuitively, JSON allows Array types to consist of items that are not of the same JSON type. For instance, an Array can contain strings, integers and numbers in the same ordered list. Such an Array is referred to as a tuple. An example of a schema describing a tuple is shown below. 

//Example_2_6

/*
{
    "$schema": "http://json-schema.org/draft-04/schema#",

    "id": "ExampleID-026",

    "title": "arrayExamples",

    "description": "Array Tuple Demo Schema",

    "type": "object",

    "properties":  {
  
                    "arrayObject": {
                    "type": "array",
                    "items": [
                                 {
                                  "type": "integer"
                                 }, 
                                 
                                 {
                                 "type": "string"
                                 }, 

                                 {
                                  "type": "string",
                                   "enum": ["Street", "Avenue", "Boulevard"]

                                 },
 
                                 {
                                  "type": "string",
                                   "enum": ["NW", "NE", "SW", "SE"]

                                 }
                          ],
                      "uniqueItems": true
                   }

   },

   "required": ["arrayObject"]
}

 */

The Array item consists of  1 integer and 3 strings. This example is tuple that describes an address.

The C# code used in creating this tuple is shown below.

//Example_2_6

JsonSchemaObject _json_schema_house_number = new JsonSchemaObject ("houseNumber", 
JSON_TYPE.INTEGER, string.Empty, true);


JsonSchemaObject _json_schema_street_name        = new JsonSchemaObject ("streetName", JSON_TYPE.STRING, string.Empty, true);
			
			
JsonSchemaObject _json_schema_street_type   = new JsonSchemaObject ("streetType", 
JSON_TYPE.STRING, string.Empty, true);

_json_schema_street_type .AddEnumList (new List<string>(new string[] {"Street","Avenue","Boulevard"}));


JsonSchemaObject _json_schema_direction   = new JsonSchemaObject ("direction", JSON_TYPE.STRING, string.Empty, true);

_json_schema_direction .AddEnumList (new List<string>(new string[] {"NW","NE","SW","SE"}));


JsonSchemaObject  _json_schema_array   = ew JsonSchemaObject ("arrayObject",null,null,true,true,string.Empty,true);

_json_schema_array.AddObject (_json_schema_house_number);
_json_schema_array.AddObject (_json_schema_street_name);
_json_schema_array.AddObject (_json_schema_street_type);
_json_schema_array.AddObject (_json_schema_direction);


JsonSchemaObject _json_schema_object = new JsonSchemaObject (JsonUtilities.JSON_VERSION.V4, "arrayExamples","Array Tuple Demo Schema", "ExampleID-026");

_json_schema_object.AddObject (_json_schema_array);
_schema_string = _json_schema_engine.Serialize (_json_schema_object);

An instance of the schema describing the tuple is done using the code below.

//Example_2_6

_instance_object_list = new List<JsonInstanceObject> ();


_instance_object_list.Add (new JsonInstanceObject (_json_schema_house_number, 1600));

_instance_object_list.Add (new JsonInstanceObject (_json_schema_street_name, "Pennsylvania"));

_instance_object_list.Add (new JsonInstanceObject (_json_schema_street_type, "Avenue"));

_instance_object_list.Add (new JsonInstanceObject (_json_schema_direction, "NW"));


JsonInstanceObject _json_instance_array = new JsonInstanceObject (_json_schema_array, 
_instance_object_list);

_instance_object_list.Clear ();

_instance_object_list.Add (_json_instance_array);


JsonInstanceObject _json_instance_object = new JsonInstanceObject (_json_schema_object,
_instance_object_list);

_instance_string = _json_instance_engine.Serialize (_json_instance_object);

The serialized output of the instance looks like this;

//Example_2_6
/*

{
"arrayObject": [1600, "Pennsylvania", "Avenue", "NW"]
}

*/
 

Additional Items

Recall that the constructor used for creating the tuple array was JsonSchemaObject (string object_name,int? minimum_items,int? maximum_items,bool is_unique_items,bool additional_items,string description,bool is_required = false). To understand the role played by the additional_items parameter in the constructor, consider that a provision was not made for the address tuple to contain a field for State. However, setting the additional_items parameter to true allows for extra fields to be added to a tuple during schema instantiation without explicitly defining these extra fields. In Example_2_7, the State field is added as shown below.

//Example_2_7
...

_instance_object_list.Add (new JsonInstanceObject (_json_schema_house_number, 1600));

_instance_object_list.Add (new JsonInstanceObject (_json_schema_street_name, "Pennsylvania"));

_instance_object_list.Add (new JsonInstanceObject (_json_schema_street_type, "Avenue"));

_instance_object_list.Add (new JsonInstanceObject (_json_schema_direction, "NW"));

_instance_object_list.Add (new JsonInstanceObject (_json_schema_state, "Washington"));


...
 

The serialized output is a represented below. 

//Example_2_7

/*
{
"arrayObject": [1600, "Pennsylvania", "Avenue", "NW", "Washington"]
}
*/ 

Set the tuple additional_items parameter to false in Example_2_7 to see if an exception is thrown. 

By setting the value of the additional_items parameter to true any of the 6 JSON types can be added to the tuple. However, instances may arise when additionalItems attribute must conform to a specific schema. For example, one can restrict the types that can be added to the tuple to JSON String types only. The schema below does exactly that.

/*    
{
      "$schema": "http://json-schema.org/draft-04/schema#",

      "id": "ExampleID-028",

      "title": "arrayExamples",

      "description": "Array Additional Items Demo Schema",

      "type": "object",

       "properties":  {
               "arrayObject": {
                        "type": "array",
                         "items": [
                                 {
                                   "type": "integer"
                                   },

                                   {
                                     "type": "string"
                                   },
 
                                    {
                                      "type": "string",

                                       "enum": ["Street", "Avenue", "Boulevard"]
                                    }, 

                                    {
                                       "type": "string",

                                       "enum": ["NW", "NE", "SW", "SE"]
                                    },
                            ],

                            "uniqueItems": true,

                             "additionalItems": 
                              {
                               "type": "string"
                              }
               }
     },

      "required": ["arrayObject"]
     }

  */

 Notice that the additionalItems attribute of the array now has a schema as value as opposed to a Boolean value assigned previously.

To set the value of an additional items attribute to a schema use the second array constructor i.e., JsonSchemaObject (string object_name,int? minimum_items,int? maximum_items,bool is_unique_items,JsonSchemaObject additional_items,string description,bool is_required = false),  to instantiate the JSON Array object as is done in Example_2_8. See the code below.

//Example_2_8
...

JsonSchemaObject _json_schema_state  = new JsonSchemaObject ("state", JSON_TYPE.STRING, 
string.Empty, true);


JsonSchemaObject  _json_schema_array  = new JsonSchemaObject ("arrayObject",null,null,true,
_json_schema_state,string.Empty,true);

...

Note that the default value for the additionalItems attribute is true. When the additionalItems attribute is not present in a JSON Array schema, it is assumed to be set to true.

If the additionalItems property is of Boolean type its value can be read from the AdditionalItemsBool property of the JsonSchemaObject class. If it is of schema type then it should be read from the AdditionalItemsObject property of the JsonSchemaObject class.

JSON Object Type Attributes

This section presents all attributes of a JSON Object type.

Additional Properties

The additionalProperties attribute is to a JSON Object type schema what additionalItems is to a JSON Array type schema. And like the addItems for JSON Array, the  additionalProperties attribute can assume either a Boolean or schema value. The default value for additionalProperties is true and it is assumed to be set to true when it is not present as an attribute in a JSON Object schema. In the schema shown below the additionalProperties is assumed to be true.

//Example_2_9
   
{
    "$schema": "http://json-schema.org/draft-04/schema#",

    "id": "ExampleID-029",

    "title": "objectExamples",

    "description": "OBJECT Additional Properties Demo",

    "type": "object",

    "properties":  {

               "jsonObject": {
                     "type": "object",

                      "properties":  {
                         "houseNumber": {
                                "type": "integer"
                           },

                           "streetName": {
                                  "type": "string"
                             },

                              "streetType": {
                                      "type": "string",

                                       "enum": ["Street", "Avenue", "Boulevard"]
                               },

                               "direction": {
                                        "type": "string",

                                     "enum": ["NW", "NE", "SW", "SE"]
                                 }
                        },
                        "required": ["houseNumber", "streetName", "streetType","direction"]
                 }
  },

  "required": ["jsonObject"]
}

Notice in the schema presented above that the jsonObject properties are similar to those of the array tuple i.e., arrayObject,  discussed in the previous section (See the Example_2_8 routine).

The JsonSchemaObject (string object_name,int? minimum_properties,int? maximum_properties,JsonSchemaObject additional_properties,string description,bool is_required = false) constructor was used to instantiate the jsonObject as shown in the code below.

//Example_2_9

JsonSchemaObject _json_schema_house_number = new JsonSchemaObject ("houseNumber", 
JSON_TYPE.INTEGER, string.Empty, true);


JsonSchemaObject _json_schema_street_name        = new JsonSchemaObject ("streetName", JSON_TYPE.STRING, string.Empty, true);
			
			
JsonSchemaObject _json_schema_street_type   = new JsonSchemaObject ("streetType", 
JSON_TYPE.STRING, string.Empty, true);

_json_schema_street_type .AddEnumList (new List<string>(new string[] {"Street","Avenue","Boulevard"}));


JsonSchemaObject _json_schema_direction   = new JsonSchemaObject ("direction", JSON_TYPE.STRING, string.Empty, true);

_json_schema_direction .AddEnumList (new List<string>(new string[] {"NW","NE","SW","SE"}));



_JsonSchemaObject  _json_schema_sub_object =  new JsonSchemaObject ("jsonObject",null,null,true,string.Empty,true);

_json_schema_sub_object.AddObject (_json_schema_house_number);
_json_schema_sub_object.AddObject (_json_schema_street_name);
_json_schema_sub_object.AddObject (_json_schema_street_type);
_json_schema_sub_object.AddObject (_json_schema_direction);


JsonSchemaObject _json_schema_object = new JsonSchemaObject (JsonUtilities.JSON_VERSION.V4, 
"objectExamples","OBJECT Additional Properties Demo", "ExampleID-029");

_json_schema_object.AddObject (_json_schema_sub_object);

_schema_string = _json_schema_engine.Serialize (_json_schema_object);

Since the additionalProperties parameter defaults to true, the state property can be added to an instance of the schema above as shown in the serialized instance text below.
 

//Example_2_9

/*

{
    "jsonObject":  {
    "houseNumber": 1600, 
    "streetName": "Pennsylvania", 
    "streetType": "Avenue", 
    "direction": "NW", 
    "state": "Washington"
    }
}
*/
 

Set the additionalProperties  parameter to false and run the Example_2_9 routine to see the exception thrown.

As was said previously, the additionalProperties attribute can also have a value that is a schema to restrict the JSON types that can be added during schema instantiation. To do this use the JsonSchemaObject (string object_name,int? minimum_properties,int? maximum_properties,JsonSchemaObject additional_properties,string description,bool is_required = false) constructor.  If this constructor is used then the serialized output of the schema should look like the text shown below.

//Example_2_10

/*   
{
           "$schema": "http://json-schema.org/draft-04/schema#",
           "id": "ExampleID-0210",
           "title": "objectExamples",
           "description": "OBJECT Additional Properties Demo",
           "type": "object",
            "properties":  {
                            "jsonObject": {
                                    "type": "object",
                                 "properties":  {
                                      "houseNumber": {
                                         "type": "integer"
                                       },

                                        "streetName": {
                                         "type": "string"
                                         },

                                          "streetType": {
                                              "type": "string",

                                               "enum": ["Street", "Avenue","Boulevard"]
                                            },

                                            "direction": {
                                               "type": "string",

                                                 "enum": ["NW", "NE", "SW","SE"]
                                            }
                                   },
                               "required": ["houseNumber", "streetName", "streetType",
                                           "direction"],
                                          
                               "additionalProperties": 
                                {
                                     "type": "string"
                                }
                              }
          },

         "required": ["jsonObject"]
}
			
*/

If the JSON Object that is to be instantiated is a top level object use either of the following constructors instead:

  1. JsonSchemaObject (JSON_VERSION version,string title,string description,string object_id,bool additional_properties = true) or
  2. JsonSchemaObject (JSON_VERSION version,string title,string description,string object_id,JsonSchemaObject additional_properties).

The additionalProperties parameter can be read from either the AdditionalPropertiesBool or AdditionalPropertiesObject properties of the JsonSchemaObject class, depending on its value type. 

Pattern Properties

Beyond restricting the additional properties value by setting it to a particular schema definition, the JSON schema specification outlines another mechanism called Pattern Properties that gives another level of control on what structure additional properties can take.  Pattern Properties is best explained by way of an example using the code fragment below.

/Example_2_13


/*  
    {
        "$schema": "http://json-schema.org/draft-04/schema#",

        "id": "ExampleID-0213",

        "title": "objectExamples",

        "description": "OBJECT Pattern Properties Demo",

        "type": "object",

        "patternProperties": {

                      "^S_[a-zA-Z0-9]*$": {
                               "type": "string",
                               "minLength": 1,
                               "maxLength": 255
                        },
 
                        "^N_[a-zA-Z0-9]*$": {
                               "type": "number",
                               "minimum": 1,
                               "maximum": 80,
                               "multipleOf": 5,

                          }
       }
}

*/
 

The sub attributes of the patternProperties attribute above enforces a rule that says any additional property that has a name that begins with "S_" must be of JSON type String and must have a character length that is not less than 1 and not greater than 255. Correspondingly, any additional property with names beginning with "N_" must be of JSON type NUMBER, must have a value between 1 and 80 and must be a multiple of 5.

The schema above is generated using the code fragment below (See the Example_2_13 routine). 

 //Example_2_13
  
JsonSchemaObject _json_schema_string_specs   = new JsonSchemaObject (string.Empty,1,255,
string.Empty,string.Empty, false);


JsonSchemaObject _json_schema_number_specs = new JsonSchemaObject (string.Empty, 
JSON_TYPE.NUMBER,1,80,5.0, string.Empty,false);


JsonSchemaObject _json_schema_object = new JsonSchemaObject (JsonUtilities.JSON_VERSION.V4, "objectExamples", "OBJECT Pattern Properties Demo", "ExampleID-0213");

_json_schema_object.AddPatternProperty (_json_schema_string_specs, "S_[a-zA-Z0-9]*");
_json_schema_object.AddPatternProperty (_json_schema_number_specs, "N_[a-zA-Z0-9]*");


_schema_string = _json_schema_engine.Serialize (_json_schema_object);

patternProperties are added to a JSON Object type using the AddPatternProperty (JsonSchemaObject json_schema_object, string property_pattern) routine as demonstrated above.

An instance of the schema is created using the code fragment below

//Example_2_13
    
JsonInstanceObject _json_instance_string =  new JsonInstanceObject (new JsonSchemaObject("S_24",JSON_TYPE.STRING,string.Empty), "Donkey Hotey");
  
JsonInstanceObject _json_instance_number =  new JsonInstanceObject (new JsonSchemaObject("N_25",JSON_TYPE.NUMBER,string.Empty),60);


JsonInstanceObject _json_instance_object = new JsonInstanceObject (_json_schema_object,
new List<jsoninstanceobject> (new JsonInstanceObject [] {_json_instance_number, _json_instance_string}));

 
_instance_string = _json_instance_engine.Serialize (_json_instance_object);

The values of  _json_instance_number and _json_instance_string objects are consistent with the restrictions placed by the patternProperties definition. Change the value passed to _json_instance_number to 100. Any idea why the exception was thrown?  

Below is the serialized textual representation of the instance of the schema.

//Example_2_13

/*    
{
   "N_25": 60, 
   "S_24": "Donkey Hotey"
}
*/

See the Example_2_14, Example_2_15 and Example_2_16 routines for more examples on Pattern Properties.

Required, Minimum and Minimum Properties

The required attribute was discussed in Part 1. Formally, it is an ordered list of property names that must be present during instantiation of a schema of an Object type. In this set of classes, if property marked as required is not present during instantiation of a JSON Object schema an exception is thrown.

The minProperties and maxProperties govern the lower and upper bound of the number of properties, as it were, that a JSON Object type is allowed to have. In the schema shown below, the jsonObject attribute is allowed to have between 1 - 6 properties and no more or less.

 

//Example_2_11

/*  
   
{
    "$schema": "http://json-schema.org/draft-04/schema#",

    "id": "ExampleID-0211",

    "title": "objectExamples",

    "description": "OBJECT Max and Min Properties Demo",

    "type": "object",

    "properties":  {

                   "jsonObject": {
                                "type": "object",
                                "properties":  {

                                            "objectString": {
                                            "type": "string"
                                             }
                                 },

                                "required": ["objectString"],
                                "minProperties": 1,
                                "maxProperties": 6
                   }
   },

   "required": ["jsonObject"]
}

*/
 

See the code fragment of Example_2_11 below on how to set the minProperties and maxProperties values.

//Eaxmple_2_11
    
JsonSchemaObject _json_schema_string  = new JsonSchemaObject ("objectString", JSON_TYPE.STRING,
string.Empty, true);


JsonSchemaObject _json_schema_sub_object    = new JsonSchemaObject ("jsonObject",1,6,true,
string.Empty,true);


_json_schema_sub_object.AddObject (_json_schema_string);

JsonSchemaObject _json_schema_object = new JsonSchemaObject (JsonUtilities.JSON_VERSION.V4, "objectExamples", "OBJECT Max and Min Properties Demo", "ExampleID-0211");

_json_schema_object.AddObject (_json_schema_sub_object);

_schema_string = _json_schema_engine.Serialize (_json_schema_object);

 

The jsonObject schema instantiation code fragment below adds four extra items to the object apart from the objectString property explicitly defined in the schema.  This is allowed because the additionalProperties parameter is set to true.

//Example_2_11
    
 int _extra_properties_count = 4;

for (int i=0; i < _extra_properties_count; i++)
{
				 
	_instance_object_list.Add (new JsonInstanceObject (
    new JsonSchemaObject ("extraString-" + (i+1).ToString (), JSON_TYPE.STRING,string.Empty,
    true), "extra-string_value" + (i+1).ToString ()));
									                   

}


JsonInstanceObject _json_instance_sub_object = new JsonInstanceObject (_json_schema_sub_object, _instance_object_list);

_instance_object_list.Clear ();

_instance_object_list.Add (_json_instance_sub_object);

JsonInstanceObject _json_instance_object = new JsonInstanceObject (_json_schema_object,
_instance_object_list);

_instance_string = _json_instance_engine.Serialize (_json_instance_object); 

As can be seen in the serialized instance text below, this brings the total number of properties for the schema instance to 5. Change the value of _extra_properties_count in the Example_2_13 routine to a number greater than or equal to 6 and see if an exception is thrown when the number of properties exceed the maxProperties value.

//Example_2_11

/*    
    
{
    "jsonObject":  {
               "objectString": "main-string_value", 
               "extraString-1": "extra-string_value1", 
               "extraString-2": "extra-string_value2", 
               "extraString-3": "extra-string_value3", 
               "extraString-4": "extra-string_value4"
     }
}

 */

The minProperties and maxProperties values can be read from the MinimumProperties and MaximumProperties properties of the JsonSchemaObject class.

Dependencies 

//Example_2_12

/*
{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "id": "ExampleID-0212",
    "title": "objectExamples",
     "description": "OBJECT Dependencies Properties
     "type": "object",
     "properties":  {
                     "billingObject": {
                          "type": "object",

                           "properties":  {

                                  "holderName": {
                                     "type": "string"
                                    },

                                    "creditCard": {
                                       "type": "number"
                                     },

                                     "billingAddress": {
                                         "type": "string"
                                     }
                           }
                        }
   },

   "required": ["billingObject"]
}


 */

From the schema above notice that the billingObject has 3 properties i.e., holderName, creditCard and billingAddress. Whilst the billingObject is required to be present, its 3 properties are not.

Reflect on a scenario where one JSON Object property cannot exist without another. For example, if the creditCard property is present then the billingAddress property must also be present and vice-versa. In other words, the creditCard and the billingAddress properties are dependent on one another.  The JSON schema validation specification can enforce this rule using the dependencies keyword. 

The JSON schema validation specification defines two types of dependencies: Schema and Property. These two dependencies types are functionally similar. The code below shows how to implement dependencies using the set of classes. 

//Example_2_12

JsonSchemaObject _json_schema_name  = new JsonSchemaObject ("holderName", JSON_TYPE.STRING, 
string.Empty, false);

JsonSchemaObject _json_schema_credit_card= new JsonSchemaObject ("creditCard", JSON_TYPE.NUMBER, string.Empty, false);

JsonSchemaObject _json_schema_billing_address  = new JsonSchemaObject ("billingAddress", 
JSON_TYPE.STRING, string.Empty, false);

JsonSchemaObject _json_schema_billing_object    = new JsonSchemaObject ("billingObject", null,
 null,true,string.Empty, true);

_json_schema_billing_object .AddObject(_json_schema_name);
_json_schema_billing_object .AddObject(_json_schema_credit_card);
_json_schema_billing_object .AddObject(_json_schema_billing_address);


List<jsonschemaobject> _schema_dependency_list = new List<jsonschemaobject> ();

_schema_dependency_list.Add (_json_schema_name);
_schema_dependency_list.Add (_json_schema_billing_address);

JsonSchemaObject _json_schema_credit_card_dependency = new JsonSchemaObject(_json_schema_credit_card,_schema_dependency_list);

_schema_dependency_list.Clear ();

_schema_dependency_list.Add (_json_schema_credit_card);
		

JsonSchemaObject _json_schema_billing_address_dependency = 
new JsonSchemaObject(_json_schema_billing_address,_schema_dependency_list );

_json_schema_billing_object.AddDependencies (JSON_DEPENDENCY_TYPE.PROPERTY,
			            new List<JsonSchemaObject> (new JsonSchemaObject[] {
                        _json_schema_credit_card_dependency,
				        _json_schema_billing_address_dependency}));

/* Line 1

_json_schema_billing_object.AddDependencies (JSON_DEPENDENCY_TYPE.SCHEMA,
			               new List<JsonSchemaObject> (new JsonSchemaObject[] {
                           _json_schema_credit_card_dependency,
				           _json_schema_billing_address_dependency}));

 // */


JsonSchemaObject _json_schema_object = new JsonSchemaObject (JsonUtilities.JSON_VERSION.V4, 
"objectExamples","OBJECT Dependencies Properties Demo", "ExampleID-0212");

_json_schema_object.AddObject (_json_schema_billing_object);


_schema_string = _json_schema_engine.Serialize (_json_schema_object);

A dependency object is created using the JsonSchemaObject (JsonSchemaObject dependent_object,List<JsonSchemaObject> dependency_object_list) constructor. In the code lines above, the first parameter for this constructor is the dependent object and the second parameter is the list of objects that this dependent object is dependent on. The _json_schema_credit_card_dependency schema object above is a dependencies object that says the creditCard property is dependent on the holderName and the billingAddress properties. The _json_schema_billing_address_dependency schema object says the billingAddress property is dependent on the creditCard property.

Dependencies are added to a JSON Object type using the AddDependencies (JSON_DEPENDENCY_TYPE dependency_type,List<JsonSchemaObject> json_dependency_object_list) routine. The first parameter passed to this routine is the dependency type required. The second parameter is the list of dependencies to be added to the JSON Object.

When serialized the output string should be similar to what is shown below;

//Example_2_12


/*
{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "id": "ExampleID-0212",
    "title": "objectExamples",
     "description": "OBJECT Dependencies Properties
     "type": "object",
     "properties":  {
                     "billingObject": {
                           "type": "object",

                             "properties":  {
                                      "holderName": {
                                        "type": "string"
                                          },

                                        "creditCard": {
                                         "type": "number"
                                          },

                                          "billingAddress": {
                                           "type": "string"
                                          }

                           },
 
                          "dependencies": {

                              creditCard: [holderName, billingAddress],
                              billingAddress: [creditCard]
                           }
                        }
   },

   "required": ["billingObject"]
}


 */

The JSON schema string shown above is an example of a Property dependencies type. 

To get the representation of a Schema dependencies type shown below, comment  out  'Line 1' in the code of Example_2_12.

//Example_2_12

/*    
{
         "$schema": "http://json-schema.org/draft-04/schema#",

         "id": "ExampleID-0212",

         "title": "objectExamples",

         "description": "OBJECT Dependencies Properties Demo",

          "type": "object",

           "properties":  {

                        "billingObject": {
                              "type": "object",
                                "properties":  {
                                      "holderName": {
                                           "type": "string"
                                       },

                                        "creditCard": {
                                         "type": "number"
                                          },

                                          "billingAddress": {
                                           "type": "string"
                                          }
                            },

                            "dependencies": {
                                   creditCard: {
                                        "properties":  {
                                            "holderName": {
                                               "type": "string"
                                              },

                                              "billingAddress": {
                                                     "type": "string"
                                               }

                                            },

                                            "required": ["holderName", "billingAddress"]

                                    },

                                    billingAddress: {
                                      "properties": {
                                           "creditCard": {
                                            "type": "number"
                                             }
                                        },

                                       "required": ["creditCard"]
                                     }
                                }
                         }
           },

    "required": ["billingObject"]
}
		

*/
 

Below is an instance of the schema shown above. 

  // Example_2_12  
{
       "billingObject":  {
                "holderName": "Donkey Hotey", 
                "creditCard": 234555554444, 
                "billingAddress": "No. 2 North West Street"
        }
}
 

As a test, exclude the creditCard property when creating the instance by uncommenting "Line 2" in the Example_2_12 routine to see what exception is thrown.

JSON Schema Constriants  Attributes

 //Example_2_17

/*   
 {
    "$schema": "http://json-schema.org/draft-04/schema#",
    "id": "ExampleID-0217",
    "title": "objectExamples",
    "description": "OBJECT Constraint Demo",
    "type": "object",
    "properties":  {

         "anonObject": {

              "oneOf": [
                  {
                    "type": "string"
                  },
 
                  {
                   "type": "integer"
                  },

                 {
                  "type": "boolean"
                 }
            ]
       }
   },

   "required": ["anonObject"]
}

*/

The interpretation of the schema above says the anonObject attribute can be of type String, Integer or Boolean during its schema instantiation. The oneOf keyword gives flexibility to a JSON attribute but with some constraints. Flexibility in that an attribute can be of one JSON type or another and still be valid.  And constraint, in the sense that this flexibility is bounded by the types defined within the oneOf keyword.

Apart from the oneOf constraint, the JSON specification defines three types of constraint i.e, not, allOf and anyOf. See the itemized explanations of these constraints below; 

  1. oneOf : The instance of the schema must match one and only one of the schemas (schemata) defined by the keyword.
  2. not: The instance of the schema can take any type except for those defined by the keyword.
  3. anyOf: The instance of the schema can match any one of the schemas (schemata) defined by the keyword.
  4. allOf: The instance of the schema must match all schemas (schemata) defined by this keyword.

The code fragment used to create and serialize the anonObject schema text above is as shown below.

//Example_2_17
    
JsonSchemaObject _json_schema_object_string  = new JsonSchemaObject ("stringObject",
JSON_TYPE.STRING,string.Empty);

JsonSchemaObject _json_schema_object_integer = new JsonSchemaObject ("integerObject",
JSON_TYPE.INTEGER ,null, null, null, string.Empty, true);

JsonSchemaObject _json_schema_object_bool    = new JsonSchemaObject ("boolObject",
JSON_TYPE.BOOLEAN,string.Empty);


JsonSchemaObject _json_schema_constraint =  new JsonSchemaObject ("anonObject", 
JSON_CONSTRAINT_TYPE.ONEOF, string.Empty,true);

_json_schema_constraint.AddObject (_json_schema_object_string);
_json_schema_constraint.AddObject (_json_schema_object_integer);
_json_schema_constraint.AddObject (_json_schema_object_bool);


JsonSchemaObject _json_schema_object = new JsonSchemaObject (JsonUtilities.JSON_VERSION.V4, 
"constraintExamples", "OBJECT Constraint Demo", "ExampleID-0217");

_json_schema_object.AddObject (_json_schema_constraint);

_schema_string = _json_schema_engine.Serialize (_json_schema_object); 

Observe that the anonObject object is instantiated using this constructor: JsonSchemaObject(string object_name,JSON_CONSTRAINT_TYPE object_constraint,string description,bool is_required = false). Notice that the second parameter passed to the constructor is the constraint type i.e.,  JSON_CONSTRAINT_TYPE.ONEOF. The schemas (schemata) for the constraints are added using the AddObject ( JsonSchemaObject json_schema_object) routine as can be seen above.

The anonObject attribute is instantiated using the code below.

 //Example_2_17
  
JsonInstanceObject _json_instance_anon_object = null;

_json_instance_anon_object =  new JsonInstanceObject (new JsonSchemaObject("anonObject",
JSON_TYPE.BOOLEAN,string.Empty),true);


/* Line 1

_json_instance_anon_object = new JsonInstanceObject (new JsonSchemaObject ("anonObject",
 JSON_TYPE.NUMBER, string.Empty), 60);

 //*/

 JsonInstanceObject _json_instance_object = new JsonInstanceObject (_json_schema_object,
 new List<jsoninstanceobject> (new JsonInstanceObject [] {_json_instance_anon_object}));


_instance_string = _json_instance_engine.Serialize (_json_instance_object);    

In the code above, the anonObject value is set to type Boolean, which of course is one of the three valid schema set in the oneOf attribute. Comment out the portion marked Line 1 in the Example_2_17 routine and run that example again. Any idea on why that exception is thrown?

The serialized output of the anonObject schema instance is shown below.

//Example_2_17

  /*  
{
  "anonObject": true
}

*/
 

For a more involved example of the JSON schema constraint see the Example_2_18 routine. 

Points of Interest

Same here as in Part 1 ;)

History

01/04/2015: First Version.

02/04/2015: Made changes to the JSON String type schema constructor.

03/05/2016: Modified the DoEscape function of this JSON library to align with the www.json.org escape specification.

03/11/2016: Corrected the JSON de-escape function. Repleaced the attached codes with the Net Core version.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here