JSON Bean Definitions
Contents |
When developing your application, you might want to describe the structure of the data model you are using. This can be very important for two reasons:
- data validation: in some cases it is useful to perform a client side validation of data coming from the user or from the server.
- Data normalization: adding default values automatically can spare you a lot of effort for checking whether some data portions have been defined or not.
- Documentation: your code could be more readable if you were able to define properly the type of data that you expect in the signature of a method.
Aria Templates allows you to define your own JSON Schemas by means of Bean definitions. The framework uses these schemas extensively: just to mention a couple of examples, widget configurations and AJAX request parameters are normalized against bean definitions.
Remark: In this article the words bean definition, schema and data type are used as sinonyms.
Bean definitions
A bean definition is a Json object that contains all the information on the data type that you want to define.
Bean definitions are grouped into packages. In order to define a package you have to use the Aria.beanDefinitions method of the Aria singleton object. Let us introduce an example of usage that will allow to explain in more detail how to specify bean definitions.
File /core/beans/SimpleBean.js
$package : "ariadoc.snippets.core.beans.SimpleBean",
$description : "Definition of a simple bean",
$namespaces : {
"json" : "aria.core.JsonTypes"
},
$beans : {
"Name" : {
$type : "json:String",
$description : "Name of a person"
},
"Age" : {
$type : "json:Integer",
$description : "Age of a person"
}
}
This method allows to define the package ariadoc.snippets.core.beans.SimpleBean which contains the two bean definitions Name and Age.
Each bean definition contains the mandatory $type property. It represents the parent type of the data type that you want to define. The parent type is described by means of a string like 'namespace:value', where the namespace refers to the package in which the corresponding parent bean definition is set, and the value refers to the bean definition itself. It is advisable to define the namespaces corresponding to the packages that you will be using in the $namespaces property of the bean definition.
In the above example, the parent type of the schema Name is the type String whose schema is defined in the package aria.core.JsonTypes. On top of that, the bean definition Name has its own properties $description, $sample and $mandatory. This means that Name inherits all the properties of its parent bean aria.core.JsonTypes.String and adds or overrides the parent $description, $sample and $mandatory properties.
This bean inheritance mechanism is the very core of beans definitions in Aria Templates. Every data type that you define (through a bean definition) will have to inherit from another data type (specified in another bean definition).
Built-in data types
Aria Templates provides a set of built-in data types whose bean definitions are available in the package aria.core.JsonTypes. Every other schema is a descendant of one of these schemas. Technically, these types are characterized by the fact that they inherit from themselves.
There are two categories of built-in types (click on the links to learn more about them):
- simple types:
String,Boolean,Integer,Float,Date,RegExp,ObjectRef,FunctionRef,JsonProperty,Enum - complex types:
Object,Array,Map,MultiTypes
When creating your bean definition, you can always include the following properties (an explanation is also available here):
$type: the mandatory parent type$description: a short literal description of your data type. It is mandatory when inheriting directly from a built-in data type. When inheriting from a user-defined schema, it is not necessarily required.$sample: an example$mandatory: a boolean telling whether the element has to be provided for the Json object containing it to be valid. More detail on validation will be provided later.$default: the default value. If the data is mandatory, it obviously does not make sense to provide a default value.
Furthermore, depending on the built-in type that you want your schema to inherit from, some additional properties can be specified (and others are automatically added by the framework). Most of them are useful for validation purposes.
For example, when using the Integer type, you might want to specify the allowed range (minimum and maximum value), or when using a String type as parent you might want to specify a regular expression that it has to match in order to be valid. The basic example introduced earlier can be enhanced a little by adding extra properties to the bean definitions:
File /core/beans/AnotherSimpleBean.js
$type : "json:String",
$description : "Name of a person",
$sample : "Jean",
$mandatory : true,
$regExp : /^[a-zA-Z]+$/
},
"Age" : {
$type : "json:Integer",
$description : "Age of a person",
$sample : 30,
$default : 25,
$minValue : 18,
$maxValue : 99
The list of extra properties that can be added to the schema according to the ancestor built-in type can be found here
Examples
In this section we will introduce more complex examples involving complex built-in data types like Arrays, Objects and MultiTypes, as well as the inheritance from a user-defined schema. Consider the following example:
File /core/beans/BaseContactBeans.js
$package : "ariadoc.snippets.core.beans.BaseContactBeans",
$description : "Definition of the basic beans used for contacts",
$namespaces : {
"json" : "aria.core.JsonTypes"
},
$beans : {
"Address" : {
$type : "json:Object",
$description : "Description of an address",
$properties : {
"city" : {
$type : "json:String",
$description : "City name",
$sample : "Chicago",
$mandatory : true
},
"country" : {
$type : "json:String",
$description : "Country name",
$sample : "USA",
$default : "Canada"
}
}
},
"BaseContact" : {
$type : "json:Object",
$description : "Basic description of a contact",
$properties : {
"name" : {
$type : "json:String",
$description : "First name",
$mandatory : true
},
"dateOfBirth" : {
$type : "json:Date",
$description : "Date of birth",
$sample : "09/03/1984"
},
"address" : {
$type : "json:Array",
$description : "List of complete addresses of a contact",
$contentType : {
$type : "Address",
$description : "Address of a contact"
}
}
}
}
}
The following explanatory remarks might be useful:
- when using type
"json:Object", the$propertiesproperty is a Json object whose values are themselves bean definitions to describe the type of expected data. Thus, another bean definition is automatically available at, for example,Address.$properties.city. - When using type
"json:Array"(seeBaseContact.$properties.address), a$contentTypecan be specified as a schema (available atBaseContact.$properties.address.$contentType). -
BaseContact.$properties.address.$contentTypeis a bean definition that inherits from the user-defined bean definitionAddress. In particular, it overrides only its$description.
Consider the following example:
File /core/beans/ContactBeans.js
$package : "ariadoc.snippets.core.beans.ContactBeans",
$description : "Definition of the beans used for contacts",
$namespaces : {
"json" : "aria.core.JsonTypes",
"baseContact" : "ariadoc.snippets.core.beans.BaseContactBeans"
},
$beans : {
"Person" : {
$type : "baseContact:BaseContact",
$description : "Advanced description of a contact",
$properties : {
"email" : {
$type : "json:String",
$description : "Email address",
$regExp : /^\S+@\S+$/
}
}
},
"Animal" : {
$type : "baseContact:BaseContact",
$description : "Advanced description of a contact",
$properties : {
"type" : {
$type : "json:String",
$description : "Type of animal",
$sample : "monkey",
$mandatory : true
}
}
},
"Group" : {
$type : "json:Map",
$description : "Composition of a group",
$contentType : {
$type : "json:MultiTypes",
$description : "Element of a group",
$contentTypes : [{
$type : "Person"
}, {
$type : "Animal"
}]
}
}
}
Remarks:
- the package
ariadoc.snippets.core.beans.BaseContactBeansis included in the$namespacesproperty of the bean definition. This allows to use schemas defined in that package. - The
PersonandAnimalschemas inherit from the typeBaseContactdefined in theariadoc.snippets.core.beans.BaseContactBeanspackage. They add a property to the$propertiesof their parent type. - Bean
Groupinherits from the built-in complex type"json:Map". The difference between a Map and an Object is that in a Map there is no constraint to the keys that can be used. TheGroup.$contentTypeschema describes the type of the valid values. - Bean
Group.$contentTypeinherits from the built-in complex type"json:MultiTypes". This special type allows to specify alternative types for a data. In this case the allowed types inherit fromPersonorAnimal.
This special type allows you to specify the different alternative types that are allowed.
Validation
The main purpose of defining schemas is to check the validity of data. Aria Templates allows you to perform data model validation by means of the class aria.core.JsonValidator. There are two methods of this class that you can use for this purpose:
-
check: checks that the provided json object complies with a certain bean definition. -
normalize: checks that the provided json object complies with a certain bean definition and adds default values. If you look at the method signature, you will notice that it accepts a second parameter which tells whether to throw errors or not.
For performance reasons, in both cases the check is performed only when you are working in debug mode. Otherwise, the check method always returns true and the normalize method only applies default values when specified in the bean definition.
Warning: At the moment Aria Templates does not support validation for schemas that inherit from aria.core.JsonTypes.MultiTypes because normalization can be ambiguous in some cases. Hence normalization for those types of data will be automatically skipped. This feature might be added in the future.
In order to provide an example that interacts with the previously defined schemas, a class that performs some data normalization is reported here.
File /core/beans/GroupManager.js
* Handles a group of animals and people
* @class ariadoc.snippets.core.beans.GroupManager
* @singleton
*/
Aria.classDefinition({
$classpath : 'ariadoc.snippets.core.beans.GroupManager',
$singleton : true,
$dependencies : ["ariadoc.snippets.core.beans.ContactBeans"],
$constructor : function () {
/**
* Group managed by the class
* @type {ariadoc.snippets.core.beans.ContactBeans.Group}
* @private
*/
this.__group = {};
},
$destructor : function () {
this.__group = null;
},
$statics : {
/**
* Error raised when trying to add an invalid contact
* @type {String}
*/
INVALID_CONTACT : 'Contact with id %1 could not be added to the group'
},
$prototype : {
/**
* Adds a person to the group
* @param {String} id unique identifier inside the group
* @param {ariadoc.snippets.core.beans.ContactBeans.Person} person
*/
addPersonToGroup : function (id, person) {
try {
aria.core.JsonValidator.normalize({
json : person,
beanName : "ariadoc.snippets.core.beans.ContactBeans.Person"
}, true);
} catch (ex) {
// The person object does not match the bean
this.$logError(this.INVALID_CONTACT, id);
return null;
}
this.__group[id] = person;
},
/**
* Adds an animal to the group
* @param {String} id unique identifier inside the group
* @param {ariadoc.snippets.core.beans.ContactBeans.Animal} animal
*/
addAnimalToGroup : function (id, animal) {
try {
aria.core.JsonValidator.normalize({
json : animal,
beanName : "ariadoc.snippets.core.beans.ContactBeans.Animal"
}, true);
} catch (ex) {
// The person object does not match the bean
this.$logError(this.INVALID_CONTACT, id);
return null;
}
this.__group[id] = animal;
}
}
});
You can see that:
- the package
ariadoc.snippets.core.beans.ContactBeanscontaining useful bean definitions is included in the dependencies of the class. This allows to load and preprocess the bean definitions, so that they are available to theJsonValidatorclass. - the
normalizemethod is surrounded by a try...catch statement. It is a good practice to do so when the second parameter passed to the method is true.
Beans for documentation purposes
As you can see in the previous code snippet, bean definitions allow you to associate a more adequate type to methods parameters or class properties.
Bean definitions inside classes
It is also possible to specify class-specific bean definitions inside the $beans key of the classDefintion configuration Json object. However, the schemas thus defined cannot be used in any way at the moment.
