Wiki

Clone wiki

limeds-framework / Guide to JSON Validation

Table Of Contents

The input and output JSON formats of FunctionalSegments can be documented using the @Documentation annotation in Java and through the config window of the Visual Editor for non-Java implementations.

The next step is to use these meta JSON-objects as a way of validating the actual input and output of the Segments at runtime.

The following sections explain the format for JSON documentation that will serve as the basis for the validation. This will be the preferred format for all JSON documentation!

JSON Type Format

In contradiction to more elaborate JSON descriptions formats such as JSON schema, our goal is to keep the format simple and representative of how an actual instance of a JSON item that matches the format looks, while still retaining a valid JSON structure.

Primitive types

{
  "example1" : "Boolean (Each field describing a primitive, has as value a String that contains the type of the field and a description of the field between parenthesis.)",
  "example2" : "String (This describes a String field, other possible types are: Boolean, IntNumber and FloatNumber.)",
  "example3" : "IntNumber * (By default, each described field is required. Use an asterix between the type and the description to indicate that the field is optional.)",
  // The description can be omitted if the meaning of the field is clear:
  "example4" : "FloatNumber",
  // If the field is restricted to a limited number of values, these values can be listed between brackets:
  "example5" : "String [train, tram, bus]",
  "example6" : "FloatNumber * [0.1, 0.365, -10.9]"
}

Note that the comments in the above example are used to clarify the example and cannot actually be used in a JSON message as it is not valid JSON!

JSON objects

{
  "objectExample1" : {
    "@typeInfo" : "Object (The @typeInfo field can be used to describe the object as a whole and will be ignored in the validation process.)",
    "field1" : "String",
    "field2" : "Boolean" 
  },
  "objectExample2" : {
    "@typeInfo" : "Object * (Using this construct, one can also indicate that the field that includes this object is optional, by adding the asterix character.)",
    "exampleField" : "IntNumber"
  }
}

JSON arrays

An array is represented in the same way as it would occur in an actual JSON instance, but instead of multiple values of a certain type, it will only contain a single item that represents the type of the potential contents of the array. In reality, this item will either be a primitive type description, an object description or another array description.

Note: when the item that is contained in the array is marked as optional, this applies to the field that holds the array (see also the example below)!

{
  "arrayExample1" : ["String (The field arrayExample1 has an array of Strings as its value.)"],
  "arrayExample2" : [{
    "@typeInfo" : "Object (We use the @typeInfo field to indicate the field arrayExample2 has an array of Objects of this specific type as its value.)",
    "field1" : "String",
    "field2" : "FloatNumber"
  }],
  "arrayExample3" : ["String * (This descriptor indicates that the field arrayExample3 contains an array of Strings, but is an optional field.)"]
}

References

The format allows references to other JSON documentation objects through the @typeRef field, e.g.:

{
  "example1" : "String (The address field below is defined in a remote JSON documentation file)",
  "address" : {
    "@typeRef" : "examples.common.Address_1.0.0"
  }
}

The value of a @typeRef attribute is a String containing the fully-qualified name and followed by the version of the referenced type (separated by an underscore).

The validator used in LimeDS is compatible with these references and will recursively evaluate the links to process the actual field descriptors (if the types are known to the system).

Inheritance

The schema also supports basic type inheritance to model more advanced type relationships.

E.g. the type Student extends from Person:

{
  "@typeParent" : "examples.common.Person_1.0.0",
  "studentId" : "String",
  "faculty" : "String *"
}

The limeds system will then resolve this type as:

{
  "name" : "String (The person's name)",
  "age" : "IntNumber (The person's age)",
  "address" : {
    "street" : "String (Name of the street)",
    "number" : "IntNumber (House number)",
    "country" : "String * (Country name)"
  },
  "studentId" : "String",
  "faculty" : "String *"
}

Defining mappings

Sometimes there is a need to model a format where the attributes can be any values, mapping to a specific type of value, for example:

{
    "clients" : {
        "mobile" : {
            "apiKey" : "jdjdf-dkdkdf-949",
            "description" : "John's Mobile"
        },
        "laptop" : {
            "apiKey" : "jdjdf-dkdkdf-950",
            "description" : "John's Personal Laptop"
        }
    }
}

To model this type of structures, limeDS introduces the @mapping attribute, which can describe the type of the mapping value, regardless of the key that is to be used. E.g., the above JSON can be validated according to the following schema:

{
    "clients" : {
        "@mapping" : {
            "apiKey" : "String (API key of the client)",
            "description" : "String * (An optional description)"
        }
    }
}

This can be combined with references to produce short and readable schema's:

{
    "clients" : {
        "@mapping" : {
            "@typeRef" : "examples.common.clientDescriptor_1.0.0"
        }
    }
}

WARNING: at the moment, @mapping cannot be used at the top-level of the JSON structure.

Defining / Registering JSON Types

In Java

You can define JSON types in Java by simply adding JSON-files containing valid type descriptors to a package. These types are then exposed by adding an additional annotation to package-info.java.

E.g. say we want to define a Geolocation JSON type in the package "types.common", we first add the following file to this package:

Geolocation.json

{
  "latitude" : "FloatNumber (The latitude of the location)",
  "longitude" : "FloatNumber (The longitude of the location)"
}

Next, we'll expose this type by adding an @IncludeType annotation to the package-info.java file.

package-info.java

@org.osgi.annotation.versioning.Version("0.1.0")
@org.ibcn.limeds.annotations.IncludeTypes({"Geolocation.json"})
package types.common;

The type will derive its version from the package version and will be registered with the LimeDS instance upon activation of the module that includes its package. To override this version, add an @typeVersion attribute to the JSON definition:

{
  "@typeVersion" : "1.0.0",
  "latitude" : "FloatNumber (The latitude of the location)",
  "longitude" : "FloatNumber (The longitude of the location)"
}

Using the Editor

When using the editor, types are tied to Slices. When editing a Slice, click the "Open type editor" on the left to add or edit a type:

doc_1.PNG

Using the Types for Documentation / Validation

You can use the defined Types to add documentation or validation to LimeDS Segments.

The Java annotation @Documentation even has a shorthand attribute to refer to defined types. E.g. when validating the input for a Segment that should be Geolocation as defined above, we could write:

@Segment
public class POIRetriever implements FunctionalSegment {

  @Override
  @Documentation(type=DocumentationType.INPUT_0, schemaRef="types.common.Geolocation_1.0.0", validate=true)
  public JsonValue apply(JsonValue... input) {
    //Process the location to produce POIs
  }

}

With the above documentation statement, we indicate we want to document the first input argument (INPUT_0), which should be a Geolocation (as indicated by the schemaRef) and validation should be enabled for it.

For simpler examples you might want to declare your type directly in the documentation annotation, or declare a composite type based on references to existing types. For this you can use the "schema" attribute. E.g. the following example documents the return value of the function which is an array of Geolocations.

@Segment
public class LocationFinder implements FunctionalSegment {

  @Override
  @Documentation(type=DocumentationType.OUTPUT, schema="[{\"@typeRef\" : \"types.common.Geolocation_1.0.0\"}]")
  public JsonValue apply(JsonValue... input) {
    //Process the input to produce locations
  }

}

This is comparable with how you setup documentation in the editor using the "Documentation and validation" menu when right-clicking on a Segment. This feature of the editor will be expanded in future releases to make it more user-friendly.

Updated