Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Android

Advanced JSON Form Specification - Chapter 8: Advanced Form Features

5.00/5 (4 votes)
15 Nov 2016LGPL39 min read 17.9K   536  
A JSON form specification
This article is the eighth of eight parts introducing an advanced JSON form specification.

Chapters

Introduction

This is the last of 8 chapters and it covers the following features of this form specification:

  1. Form skip logic
  2. Form metadata collection
  3. Repeat Screens
  4. Form instance encryption

Four separate forms are attached to this article for each of the four features described.

Skip Logic

Skip logic is a feature that allows a form screen to be displayed or hidden/bypassed based on the value of the entry on a previous screen. The screens shown below are from the Skip Screen Demo form. The reader is encouraged to download and test this form using the CCA-Mobile app.

Image 1

From testing the Skip Screen Demo form, the reader would have noticed that if the integer entry of the first screen shown above is a value greater than 10, then the second form screen is shown, otherwise the third form screen is shown. In other words, the display of the second or third screen is based on the value entered in the first screen.

The JSON form definition that allows for this possibility is described using the Skip Screen Demo form example shown in the code block below:

JavaScript
/*

{
"formName": "Skip Screen Demo", 
"formID": "0fe3f144-d242-4959-9473-7098719029cd", 
"formDescription": "This is the example form for Chapter 8 (Skip Screen)", 
"canSavePartial": true, 
"formScreens": [
{
"mainScreen":  {
"screenID": "A Number", 
"screenDisplayArray": [
{
"localeCode": "en", 
"screenLabel": "1. Enter an even number", 
"screenHint": "This number must be between 1 - 20"
}], 
"screenwidgetType": "integerInput", 
"inputRequired": true, 
"widgetSchema": "
\"A Number\": {
\"type\": \"integer\",
\"minimum\": 1,
\"maximum\": 20,
\"multipleOf\": 2
}"
}
}, 
{
"mainScreen":  {
"screenID": "Info 1", 
"screenDisplayArray": [
{
"localeCode": "en", 
"screenLabel": "Please read the text below"
}], 
"screenwidgetType": "info", 
"inputRequired": false, 
"widgetSchema": "
\"Info 1\": {
\"type\": \"string\"
}", 
"screenBindObject":  {
"screenID": "A Number", 
"widgetName": "A Number", 
"widgetValue": ["10"], 
"bindScreenOperator": ">"
}, 
"infoDisplayArray": [
{
"localeCode": "en", 
"localeText": "You entered a number greater than 10!"
}]
}
}, 
{
"mainScreen":  {
"screenID": "Info 2", 
"screenDisplayArray": [
{
"localeCode": "en", 
"screenLabel": "Please read the text below."
}], 
"screenwidgetType": "info", 
"inputRequired": false, 
"widgetSchema": "
\"Info 2\": {
\"type\": \"string\"
}", 
"screenBindObject":  {
"screenID": "A Number", 
"widgetName": "A Number", 
"widgetValue": ["10"], 
"bindScreenOperator": "<="
}, 
"infoDisplayArray": [
{
"localeCode": "en", 
"localeText": "You entered a number less than or equal to 10"
}]
}
}]
}

*/

Observe that the form is made up of three screens with screenID values: A Number, Info 1 and Info 2.

In the Info 1 screen, notice the screenBindObject parameter. This object consists of four properties, i.e., screenID, widgetName, widgetValue and bindScreenOperator. The application displaying this form carries out the following actions to determine if a screen with the screenBindObject parameter present can be displayed to the user or not.

  1. The application extracts the screenID value in the screenBindObject parameter.
  2. The application searches the form for a screen that has the screen ID value extracted in step 1.
  3. If a screen with the extracted screenID is not found, the current form screen is not displayed.
  4. On the other hand, if the screen with the extracted screenID is found, the application searches the found screen for the value of the input that matches the widgetName value in the screenBindObject parameter.
  5. If the input value is found in step 4, then the value of widgetValue in the screenBindObject parameter is compared to the value of this input. Note that the operator used for this comparison is the value of the bindScreenOperator in the screenBindObject parameter.
  6. If the comparison evaluates to true, then the screen is displayed to the user, otherwise the screen is skipped.

Explore the screenBindObject parameter for the Info 2 screen.

Form Metadata

The collection of metadata is transparent to the user. Metadata include information about the device on which the form is displayed or information on how the form is being filled by the user. Metadata, although defined as a screen, it is never displayed. The code block below shows a typical example of a JSON definition for the invisible metadata form screen.

JavaScript
/*
{
"mainScreen":  {
"screenID": "866d44b7-609f-4459-82be-7d1ef61f48b4", 
"screenDisplayArray": [
{
"localeCode": "en", 
"screenLabel": "2853e838-c0ac-42f8-860e-f8c500b798a5"
}], 
"screenwidgetType": "metaData", 
"inputRequired": false, 
"widgetSchema": "
\"metaData\": {
\"type\": \"object\",
\"properties\":  {
\"startDate\": {
\"type\": \"object\",
\"properties\":  {
\"dateYear\": {
\"type\": \"integer\",
\"minimum\": 0,
\"maximum\": 9999
},
\"dateMonth\": {
\"type\": \"integer\",
\"minimum\": 1,
\"maximum\": 12
},
\"dateDay\": {
\"type\": \"integer\",
\"minimum\": 1,
\"maximum\": 31
},
\"saveAsUtc\": {
\"type\": \"boolean\",
\"default\": false
}
},
\"required\": [\"dateYear\", \"dateMonth\", \"dateDay\"],
\"minProperties\": 3,
\"maxProperties\": 3,
\"additionalProperties\": false
},
\"endDate\": {
\"type\": \"object\",
\"properties\":  {
\"dateYear\": {
\"type\": \"integer\",
\"minimum\": 0,
\"maximum\": 9999
},
\"dateMonth\": {
\"type\": \"integer\",
\"minimum\": 1,
\"maximum\": 12
},
\"dateDay\": {
\"type\": \"integer\",
\"minimum\": 1,
\"maximum\": 31
},
\"saveAsUtc\": {
\"type\": \"boolean\",
\"default\": false
}
},
\"required\": [\"dateYear\", \"dateMonth\", \"dateDay\"],
\"minProperties\": 3,
\"maxProperties\": 3,
\"additionalProperties\": false
},
\"deviceID\": {
\"type\": \"string\"
},
\"phoneNum\": {
\"type\": \"string\"
},
\"simSerial\": {
\"type\": \"string\"
},
\"subscriberID\": {
\"type\": \"string\"
},
\"timeSpan\": {
\"type\": \"object\",
\"properties\":  {
\"days\": {
\"type\": \"integer\"
},
\"hours\": {
\"type\": \"integer\"
},
\"minutes\": {
\"type\": \"integer\"
},
\"seconds\": {
\"type\": \"integer\"
}
},
\"required\": [\"days\", \"hours\", \"minutes\", \"seconds\"],
\"minProperties\": 4,
\"maxProperties\": 4,
\"additionalProperties\": false
}
},
\"required\": [\"startDate\", \"endDate\", \"deviceID\", 
\"phoneNum\", \"simSerial\", \"subscriberID\", \"timeSpan\"],
\"additionalProperties\": false
}"
}
}

*/

The metadata screen definition above is an excerpt of the Metadata demo form attached to this article. The screenwidgetType is set to metaData. The main properties of the widgetSchema value for this screen are discussed below:

  1. startDate: This is the date on which the user began to fill the form. Note that as with date and time widgets, the form designer can specify whether this information is saved in its Coordinated Universal Time (UTC) equivalent or not.
  2. endDate: This is the date on which the user completed the form. The form designer can also specify if this date is stored in its Coordinated Universal Time (UTC) value or not.
  3. deviceID: This is the International Mobile Station Equipment Identity (IMEI) of the device, if it has one.
  4. phoneNum: This is the phone number attached to the Subscriber Identity Module (SIM), if present on the device.
  5. simSerial: This is the serial number on the Subscriber Identity Module (SIM), if present on the device.
  6. subscriberID: This is the International mobile subscriber identity (IMSI) assigned by the cellular network to the SIM.
  7. timeSpan: This is the duration in days, hours, minutes and seconds it took to complete a form.

Below is a completed instance of the Metadata Demo form.

JavaScript
/*

{
"metaData":  {
"startDate":  {
"dateYear": 2016, 
"dateMonth": 6, 
"dateDay": 28
}, 
"endDate":  {
"dateYear": 2016, 
"dateMonth": 6, 
"dateDay": 28
}, 
"deviceID": "9908876543234577", 
"phoneNum": "N/A", 
"simSerial": "76888076543244", 
"subscriberID": "877668888009888", 
"timeSpan":  {
"days": 0, 
"hours": 0, 
"minutes": 0, 
"seconds": 8
}
}
}

*/

Notice that the form was completed on the same day and took 8 seconds to complete. Also notice that the phoneNum parameter has a value of N/A. N/A is used for metadata parameters for which values are not present or cannot be gotten.

Repeat Screen

Forms have instances where a single question requires one or more answers. For example, a respondent could be asked to list the number of countries s/he has visited. Repeat screens cater to these instances by creating a number of input screens at runtime based on the number of countries entered by the respondent.

The images below are screenshots from the Repeat Screen Demo Form attached to this article as displayed on the CCA-Mobile app. On the screen on the left, the respondent enters the number 4 for the number of countries visited. The respondent is further prompted to enter the name of the countries by clicking a button. The screen on the right is one that prompts for the name of the first country. Swiping left will reveal a similar input screen that prompts for the second country name. This continues until the user enters the name of the fourth country.

Image 2

The definition for the repeat screen just described is shown in the code block below:

JavaScript
/*
{
"mainScreen":  {
"screenID": "Num Countries", 
"screenDisplayArray": [
{
"localeCode": "en", 
"screenLabel": "1. Enter the number of countries you have visited.", 
"screenHint": "Click the button below to enter the name of these countries."
}], 
"screenwidgetType": "repeat", 
"inputRequired": true, 
"widgetSchema": "
\"Num Countries\": {
\"type\": \"array\",
\"items\": 
{
\"type\": \"object\",
\"properties\":  {
\"Name of Country\": {
\"type\": \"string\"
}
},
\"required\": [\"Name of Country\"],
\"additionalProperties\": false
},
\"additionalItems\": false
}"
}, 
"subScreenList": [
{
"screenID": "Name of Country", 
"screenDisplayArray": [
{
"localeCode": "en", 
"screenLabel": "Enter the name of the country below", 
"screenHint": "Please do not leave blank"
}], 
"screenwidgetType": "textInput", 
"inputRequired": true, 
"widgetSchema": "
\"Name of Country\": {
\"type\": \"string\"
}"
}]
}
*/

Notice that the screenwidgetType is set to repeat and the widgetSchema is the escaped schema for an array that holds all the country names entered by the user.

As mentioned previously, the repeat screen replays one or more sub screens a number of times depending on the input. The sub screens definition are contained in the subScreenList JSON array of the repeat screen definition.

Notice that the schema contained in the widgetSchema of the sub screen with screenIdName of Country is the same the item of the Num Countries JSON array.

Below is an example of a completed instance of this repeat screen input.

JavaScript
/*

{
"Num Countries": [
{
"Name of Country": "Country A"
}, 
{
"Name of Country": "Country B"
}, 
{
"Name of Country": "Country C"
}, 
{
"Name of Country": "Country D"
}], 
...
}

*/

The number of times sub screens are repeated can be restricted at form design time. The screen images below restrict the number of repeats to two.

Image 3

This repeat count restriction is achieved by setting the value of the maxItems of the array contained in the widgetSchema of the repeat screen. For example, the Fave Countries array in the repeat screen definition below is set to 2.

JavaScript
/*

{
"mainScreen":  {
"screenID": "Fave Countries", 
"screenDisplayArray": [
{
"localeCode": "en", 
"screenLabel": "2. Enter the name of the top two favourite 
                of all the countries you have visited.", 
"screenHint": "Click the button below to enter the names."
}], 
"screenwidgetType": "repeat", 
"inputRequired": true, 
"widgetSchema": "
\"Fave Countries\": {
\"type\": \"array\",
\"items\": 
{
\"type\": \"object\",
\"properties\":  {
\"Country Name\": {
\"type\": \"string\"
}
},
\"required\": [\"Country Name\"],
\"additionalProperties\": false
},
\"maxItems\": 2,
\"additionalItems\": false
}"
}, 
"subScreenList": [
{
"screenID": "Country Name", 
"screenDisplayArray": [
{
"localeCode": "en", 
"screenLabel": "Enter the name of the favourite country", 
"screenHint": "Please do not leave blank."
}], 
"screenwidgetType": "textInput", 
"inputRequired": true, 
"widgetSchema": "
\"Country Name\": {
\"type\": \"string\"
}"
}
*/

Below is an example of a completed instance of the restricted repeat screen.

JavaScript
/*

{
..., 
"Fave Countries": [
{
"Country Name": "Country C"
}, 
{
"Country Name": "Country B"
}]
}

*/

Form Instance Encryption

The content of this section was one of the primary motivations for defining this JSON form specification. The idea behind this was to allow for the encryption of form instances on the device and everywhere else as soon as they are completed. This encryption mechanism needed to achieve the following objectives;

  1. Each completed form instance must be encrypted with a different encryption key. This assures that if one completed instance is compromised, other completed instances will remain secure.
  2. Encryption must be done using a robust symmetric protocol like the Advanced Encryption Standard (AES).
  3. The keys used to encrypt completed form instances are to be computed using the Diffie–Hellman (D–H) key exchange mechanism.
  4. The encrypted data must be signed using a hash algorithm e.g., SHA256.

To enable this security function, a secure cryptographic public-private key pair is generated. The private key half of this key pair is kept securely by the form designer and the public key half is Base64 encoded and included as the value of the formPublicKey parameter in the form definition. See an example in the Secure Form Demo form definition below:

JavaScript
/*

{
"formName": "Secure Form Demo", 
"formID": "d875a1c5-5962-4d5e-ae0d-8d9bad9a9b9f", 
"formDescription": "This is the example form for Chapter 8 (Secure Form)", 
"formPublicKey": "DqYOFPae0DPbburzNF7gvRJvJMtmWc3nV79Paa2ZFVpxOSvp997bwuYLMMa0OxGEOavtdpk5o0
 s84Z5mgpTz3aCZkNYo8Sdks7THWCHsQqKyoPTN2dOAqHwTxinr6VbBbK9Bjoe12Lf5gV8LZzxkFt0HFWQx23DjcG9KIe
 PnMQtIDl3iApiukDQLsMTccFFPwdr+/bcPeWazEXTUYecBPLkO1vusIqC1FcXWBTuxoM/4Cu7r4Z2MT3MnQBIt3flX", 
"canSavePartial": false, 
"formScreens": [
{
"mainScreen":  {
"screenID": "Name", 
"screenDisplayArray": [
{
"localeCode": "en", 
"screenLabel": "1. Enter candidate's name in the text box below.", 
"screenHint": "Please do not leave blank."
}], 
"screenwidgetType": "textInput", 
"inputRequired": true, 
"widgetSchema": "
\"Name\": {
\"type\": \"string\"
}"
}
}, 
{
"mainScreen":  {
"screenID": "Vote", 
"screenDisplayArray": [
{
"localeCode": "en", 
"screenLabel": "2. Please enter candidate's vote count below.", 
"screenHint": "Enter an integer value between 0 - 100."
}], 
"screenwidgetType": "integerInput", 
"inputRequired": true, 
"widgetSchema": "
\"Vote\": {
\"type\": \"integer\",
\"minimum\": 0,
\"maximum\": 100
}"
}
}]
}
*/

Observe that the canSavePartial parameter is set to false meaning that a partially completed instance of this form is not allowed.

Below is an example of a completed instance of the form defined above:

JavaScript
/*

{
"encryptedData": "jvQ5Ewa0zZInj56m3BMKh91wvL/0RVDhCYYI93bLI76rUg==", 

"dataSignature": "8PIFfjS+HVFSUWD0CVVOUr/7lUDePtF+3WJUrQ/VE7g=",

"publicKey": "fEVu+M95Qsa/tZ48svN0yuwhhaqIKj73wylMzn1aonp+0MR7HVYu5D7s3cd0mY/
 N95zKfDrN+t5BNCsiHtk60CUNU7dAIgqDdFSOOkWXDDPZ03ZUGQQkhK+MUg+XnYNd5InEpNM0Cyj99jB+
 MSIqD3UJZvhlpNK8VoYFHIv5QPw7auxAyiH7+tU6p6vzxPeyd8LqY1pb1q0fYR5CfUnuyvMET+
 iIX9pD+AFUvu3DfUFha0PiMmVv7llIDIMBYz1n"
}

*/

Completed instances of forms with the formPublicKey parameter present always take the JSON format shown above. To understand why this is the case, it is pertinent to describe how a completed instance is encrypted and stored. The process is thus:

  1. The device on which the form is displayed generates a randomly secure cryptographic public and private key pair. The public key is encoded in its Base64 format and set as the value of the publicKey JSON string variable.
  2. The device extracts the value of the formPublicKey parameter included with the form.
  3. The device uses the private key generated in step 1 and the form’s public key to compute an encryption key using the D-H key exchange mechanism. After this key is generated, the private key generated in step 1 is securely discarded.
  4. The encryption key computed in step 3 is used to encrypt the plain text completed instance of the form. The encrypted form instance is encoded in its Base64 format and set as the value of the encryptedData JSON string variable.
  5. The encrypted instance of the form is signed using a hash algorithm. The Base64 equivalent of this computed signature is set as the dataSignature JSON string variable.

To decrypt the encrypted form instance, the following steps are executed:

  1. The hash of the encryptedData JSON string value is computed. This computed value is compared to the value of the dataSignature JSON string variable. If there is a match, then the decryption process continues, otherwise it is aborted.
  2. The private key associated with the public key included in the form as well as the publicKey JSON string value are used to compute a decryption key using the D-H algorithm.
  3. The computed key is then used to decrypt the encryptedData JSON string value.

The steps described for the encryption and decryption processes are merely instructive. The implementer might decide to take another route. Having said that, the interested reader can view this link for the math behind the cryptographic process given in this section.

Note: Encryption applies to textual data only. It does not cover graphical data like photos and signatures.

Point of Interest

The interested reader should follow the form design sections of these walkthroughs and use this GUI tool to learn more about designing forms based on a number of use case scenarios.

History

  • 18th July, 2016: First version
  • 3rd November, 2016: Made corrections to this work

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)