A NIEM-JSON tutorial

This page walks through a simple example of implementing an exchange using NIEM-JSON.

Define data requirements

First, we start with a set of very simple data requirements. In this case, we’ll use a brief set of data requirements, organized hierarchically. Each sub-bullet is a property of whatever contains it. The top concept is a motor vehicle crash.

  • motor vehicle crash [1]
    • involved vehicle: vehicle [1-n]
      • vehicle identifier number (VIN) : identifier [1]
      • driver : person [1]
        • name : string [1]

Define a NIEM exchange model

Next, we search for these data requirements in the NIEM Subset Schema Generation Tool (SSGT), yielding a NIEM object model.

The SSGT is a tool that enables users to search through the NIEM data model and build a NIEM subset. There are other ways to search the model, including NIEM Movement. If you are new to NIEM, it is recommended that you take some time to understand how the data model works. See NIEM’s model reference documentation to learn more. Additional training is forthcoming.

We have searched for these data requirements in the SSGT and found corresponding NIEM elements.

You may download and save the resulting list of components as a NIEM wantlist, which you may load into the SSGT.

The NIEM representation of these data requirements contains more components than the list of source requirements above, due to the way NIEM structures its data: A crash vehicle is represented as a role of a vehicle, a crash driver is represented as a role of a person, and a person’s name and a vehicle identifier are each represented by a structure that can carry more than just a simple value.

model

The above object model shows type information and cardinality, which won’t appear in a resulting message.

Define a JSON-LD context

Our next step is to construct a JSON data example that uses the above object model. First, we construct a JSON-LD context object that identifies all of the namespace prefixes that are needed for the instance. Each NIEM namespace is converted into a URI according to the NIEM NDR, which requires that NIEM namespace URIs have the # character (i.e., number sign) appended, if one is not already present. Here is the resulting context:

{
  "j": "http://release.niem.gov/niem/domains/jxdm/6.0/#",
  "nc": "http://release.niem.gov/niem/niem-core/4.0/#"
}

Construct a sample JSON file

Next, we construct a corresponding JSON file. To do this, we walk through the above object model, adding the NIEM property names as JSON properties.

Every property that does not have a simple value (such as a string or a number) is represented as an object. For example, the j:Crash element is represented as an object:

"j:Crash" : {
  ... value for the crash property ...
}

If a property has only a single simple value, then it may be represented as a literal value, rather than an object. For example, the nc:PersonFullName element here needs only a simple value:

"nc:PersonFullName": "Gerry H. Everett"

If a property occurs more than once, then it must appear as a property with a value that is an array; each value of the array is a separate value of the property. For example, if an organization has 3 employees, it may appear as an “nc:Employee” property with a value of an array containing 3 objects, each representing a person (e.g., an object representing an instance of NIEM type nc:PersonType).

"nc:Employee": [
  { "nc:PersonFullName": "Gerry H. Everett" },
  { "nc:PersonFullName": "Floyd Q. Halverson" },
  { "nc:PersonFullName": "Malcolm P. Smith" }
]

Use JSON-LD features as needed

NIEM JSON leverages JSON-LD to provide features not present in plain JSON. For example:

  • JSON-LD contexts are used to provide namespace features. Contexts have many features this walkthrough doesn’t exercise. See the page on JSON-LD contexts* for more information.

  • You may use the property rdf:value to represent an object that has a both attributes and a simple value.

  • You may use the JSON-LD property @id to perform the function of NIEM XML’s structures:id, structures:ref, and structures:uri.

  • JSON-LD allows arrays to be used in instances very freely; it does not distinguish between an array with 1 entry and a single value. This is a property with a value that is a single string:

    "nc:PersonFullName": "Gerry H. Everett"
    

    This is a property whose value is an array containing one string:

    "nc:PersonFullName": [ "Gerry H. Everett" ]
    

    Under JSON-LD, these two examples have the same meaning. A developer needs to decide what syntax they want in instances, including how and when arrays will appear. These decisions may be enforced using a JSON schema.

  • Every property in NIEM has a type. In some instances, the actual value of a property is not fully specified by the type defined for the property in the NIEM data model. In the example above, the property nc:RoleOfItem has type nc:ItemType, but the instance requires the value to have type nc:VehicleType, which is derived from nc:ItemType. In this case, and similar cases, you may use the JSON-LD property @type to identify the type of the property’s value, as shown below.

A complete sample instance

As described above, we walk through the NIEM exchange model, adding each NIEM property as a JSON property. This results in the following JSON document:

{
  "@context" : {
    "j": "http://release.niem.gov/niem/domains/jxdm/6.0/",
    "nc": "http://release.niem.gov/niem/niem-core/4.0/"
  },
  "j:Crash": {
    "j:CrashVehicle": [
      {
        "nc:RoleOfItem": {
          "@type": "nc:VehicleType",
          "nc:VehicleIdentification": {
            "nc:IdentificationID": "5TFEM5F10EX067865"
          }
        },
        "j:CrashDriver": {
          "nc:RoleOfPerson": {
            "nc:PersonName": {
              "nc:PersonFullName": "Gerry H. Everett"
            }
          }
        }
      }, {
        "nc:RoleOfItem": {
          "@type": "nc:VehicleType",
          "nc:VehicleIdentification": {
            "nc:IdentificationID": "1FTSW2BR5AEA23373"
          }
        },
        "j:CrashDriver": {
          "nc:RoleOfPerson": {
            "nc:PersonName": {
              "nc:PersonFullName": "Chester D. Barbee"
            }
          }
        }
      }
    ]
  }
}

This example is available in the JSON-LD Playground.

If needed, construct a JSON schema for your instances

NIEM does not require the use of JSON schema for your JSON messages. NIEM merely requires that JSON documents have the correct format. However, you can create a JSON schema for your messages if you need to. Here, we’ll step through the creation of a JSON schema for JSON data like the sample above. Note that there’s nothing normative about this schema; this walkthrough is merely laying out one way to build a JSON schema from a NIEM exchange model. There are many schemas you could create for the same model.

Framework for an instance

First, we lay out the basic structure of the JSON schema. This does not have any content; there’s nothing from the NIEM exchange model we defined above. It’s a framework into which we’ll put the definitions we need to express the NIEM exchange model.

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "properties": {
  },
  "additionalProperties": false,
  "required": [
  ]
  "definitions": {
  },
}

The JSON Schema properties we are using here are:

  • $schema: Indicates the JSON Schema version being used by this schema.
  • type: Directs that this piece of a schema will only match a JSON object.
  • properties: Describes all the properties that are allowed by this piece of the schema
  • additionalProperties: Indicates that this piece of the schema only allows properties that are listed under properties.
  • required: Lists all properties that must occur in an object matching this piece of schema.
  • definitions: A holding place for pieces of schemas which can be reused.

Top-level properties

We know that we require @context and j:Crash at the top, so we add them to required:

"required": [
  "@context",
  "j:Crash"
],

We also need these to be available as properties of the top level object, so we’ll add them to properties. Rather than define their structure within properties, we’ll point them to a defintion within the definitions section of the schema. The properties @context and j:Crash are both simple; each references its definition in the definitions section of the schema.

"properties": {
  "@context": { "$ref": "#/definitions/@context" },
  "j:Crash": { "$ref": "#/definitions/j:CrashType" },
},

Definitions

Next, we write definitions for the content of the top-level properties. These definitions will be small schemas within a definitions section of the JSON schema document. First, we add the definition for @context, which will accept any object:

"@context": {
    "type": "object"
  },

The rest of the work will be adding NIEM types for j:Crash and recursively for all of its children; this will define the rest of the exchange model. Each definition (besides @context) is named after its NIEM type. We start with j:CrashType. This type adds a bit of complexity, since its property j:CrashVehicle can occur 1-n times (at least once, with no fixed maximum on the number of times it may occur). This means that j:CrashVehicle must be defined as an array:

"j:CrashType": {
  "type": "object",
  "properties": {
    "j:CrashVehicle": {
      "type": "array",
      "items": { "$ref": "#/definitions/j:CrashVehicleType" },
      "minItems": 1
    }
  },
  "additionalProperties": false,
  "required": [ "j:CrashVehicle" ]
},

j:CrashVehicle is an array with items matching the definition for type j:CrashVehicleType. The properties defining j:CrashVehicle are:

  • type: Defines this property as an array.
  • items: Indicates that each item of the array must match the definition for j:CrashVehicleType.
  • minItems: Directs that there must be at least one object within the array, matching the cardinality in the exchange model of 1-n.

This defines a piece of a schema that matches an object with a single property, j:CrashVehicle. As above, the definition used for the child element is named for the type of the element, in this case, j:CrashVehicleType:

"j:CrashVehicleType": {
  "type": "object",
  "properties": {
    "nc:RoleOfItem": { "$ref": "#/definitions/nc:VehicleType" },
    "j:CrashDriver": { "$ref": "#/definitions/j:CrashDriverType" }
  },
  "additionalProperties": false,
  "required": [ "nc:RoleOfItem", "j:CrashDriver" ]
},

We continue like this through the object model, adding only what we need to make the schema validate the sample instance. When we get to the definition for nc:PersonFullName, it has its definition from type nc:PersonNameTextType, which has a string value. This appears in the JSON schema as:

"nc:PersonNameTextType": {
  "type": "string"
},

Resulting schema

Repeating the above for the entire exchange model yields the following JSON schema. This validates the above sample instance.

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "properties": {
    "@context": { "$ref": "#/definitions/@context" },
    "j:Crash": { "$ref": "#/definitions/j:CrashType" }
  },
  "additionalProperties": false,
  "required": [
    "@context",
    "j:Crash"
  ],
  "definitions": {
    "@context": {
      "type": "object"
    },
    "j:CrashType": {
      "type": "object",
      "properties": {
        "j:CrashVehicle": {
          "type": "array",
          "items": { "$ref": "#/definitions/j:CrashVehicleType" },
          "minItems": 1
        }
      },
      "additionalProperties": false,
      "required": [ "j:CrashVehicle" ]
    },
    "j:CrashVehicleType": {
      "type": "object",
      "properties": {
        "nc:RoleOfItem": { "$ref": "#/definitions/nc:VehicleType" },
        "j:CrashDriver": { "$ref": "#/definitions/j:CrashDriverType" }
      },
      "additionalProperties": false,
      "required": [ "nc:RoleOfItem", "j:CrashDriver" ]
    },
    "j:CrashDriverType": {
      "type": "object",
      "properties": {
        "nc:RoleOfPerson": { "$ref": "#/definitions/nc:PersonType" }
      },
      "additionalProperties": false,
      "required": [ "nc:RoleOfPerson" ]
    },
    "nc:PersonType": {
      "type": "object",
      "properties": {
        "nc:PersonName": { "$ref": "#/definitions/nc:PersonNameType" }
      },
      "additionalProperties": false,
      "required": [ "nc:PersonName" ]
    },
    "nc:PersonNameType": {
      "type": "object",
      "properties": {
        "nc:PersonFullName": { "$ref": "#/definitions/nc:PersonNameTextType" }
      },
      "additionalProperties": false,
      "required": [ "nc:PersonFullName" ]
    },
    "nc:PersonNameTextType": {
      "type": "string"
    },
    "nc:VehicleType": {
      "type": "object",
      "properties": {
        "@type": { "type": "string" },
        "nc:VehicleIdentification": { "$ref": "#/definitions/nc:IdentificationType" }
      },
      "additionalProperties": false,
      "required": [ "@type", "nc:VehicleIdentification" ]
    },
    "nc:IdentificationType": {
      "type": "object",
      "properties": {
        "nc:IdentificationID": { "$ref": "#/definitions/niem-xs:string" }
      },
      "additionalProperties": false,
      "required": [ "nc:IdentificationID" ]
    },
    "niem-xs:string": {
      "type": "string"
    }
  }
}