Skip to main content

Version: 21.05 (EOL)

Object Mapper Guide

Journey Manager supports marshalling data between Form XML Documents and Groovy Value Objects using the ObjectMapper class. Where possible, we recommend you use ObjectMapper for marshalling data, rather than doing this manually, to reduce the risk of introducing errors.

ObjectMapper supports two use cases.

  1. Create a Groovy object graph from an XML document.
  2. Update an XML document based on a Groovy object graph.

ObjectMapper Configuration

ObjectMapper has a number of default configurations, which can be changed with the appropriate setter methods.

PropertyDescription
strictModeboolean

Whether a missing Object field for a Document element will throw an exception. The default value is true.

Disabling strict mode may bring some unpredictable behavior, especially with repeats when there is total mismatch of the object and XML structure. The result could be incorrect mapping of XML tags and values to objects.

mappingStrategyenum (ObjectMapper.MappingStrategy)

The XML tag name to object mapping strategy. The default value is CamelUpperCase.

Must be one of the following.

  • UPPER_CAMEL_CASE: Words are capitalized, and no separator is used between words. For example, FirstName.
  • SNAKE_CASE: Words are in lowercase letters, separated by underscores. For example, first_name.
  • LOWER_CAMEL_CASE: Words other than the first are capitalized, and no separator is used between words. For example, firstName.
  • LOWER_CASE: All words of the logical name are in lower case, and no separator is used between words. For example, firstname.
  • KEBAB_CASE: Words are in lower-case letters, separated by hyphens. For example, first-name.

For example, by default, the XML element <FirstName> maps to the object class's firstName field.

dateFormatjava.text.DateFormat

The datetime marshalling format. The default value is "yyyy-MM-dd"".

The following example sets the mapping strategy to handle XML Document's element of type lowerCamelCase, date format "yyyy/dd/MM", and switches off the strict mapping mode (that is, ignore unknown XML elements and don't throw an exception).

import com.avoka.tm.util.*
import java.text.SimpleDateFormat
new ObjectMapper()
.setMappingStrategy(MappingStrategy.LOWER_CAMEL_CASE)
.setDateFormat(new SimpleDateFormat("yyyy/dd/MM"))
.setStrictMode(false)

Groovy Examples

The ObjectMapper examples below use the following Groovy value object classes.

package com.maguire.vo
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import java.util.*
class Person {
String firstName
String lastName
String email
Address currentAddress

@JacksonXmlElementWrapper(localName = "PreviousAddresses")
@JacksonXmlProperty(localName = "Address")
List<Address> previousAddresses
}
class Address {
String line1
String city
Integer postCode
String country
}

The corresponding XML elements for these classes are provided below.

<Person>
<FirstName/>
<LastName/>
<Email/>
<CurrentAddress>
<Line1/>
<City/>
<PostCode/>
<Country/>
</CurrentAddress>
<PreviousAddresses>
<Address>
<Line1/>
<City/>
<PostCode/>
<Country/>
</Address>
</PreviousAddresses>
</Person>

Creating an Object Graph

This Groovy example creates a Person object from the Document xpath.

import com.avoka.tm.util.*
import com.maguire.vo.*
Person person = new ObjectMapper()
.setDoc(param.appDoc)
.setFormXPath('/Person')
.create(Person.class)

An example source XML document is shown below. In this example, the XML repeating PreviousAddresses elements is mapped into the Person class's previousAddresses field.

<?xml version="1.0"?>
<AvokaSmartForm>
<Person>
<FirstName>John</FirstName>
<LastName>Smith</LastName>
<Email>[email protected]</Email/>
<CurrentAddress>
<Line1>12 Peterson Avenue</Line1>
<City>Hope Town</City>
<PostCode>4275</PostCode>
<Country/>
</CurrentAddress>
<PreviousAddresses>
<Address>
<Line1>30 Peace Street</Line1>
<City>Hope Town</City>
<PostCode>4276</PostCode>
<Country/>
</Address>
<Address>
<Line1>Unit 3/65 Dunkley Avenue</Line1>
<City>Melville</City>
<PostCode>5621</PostCode>
<Country>Australia<Country>
</Address>
</PreviousAddresses>
</Person>
<AvokaSmartForm>

Support for XML Repeats

To support mapping repeating XML elements with the List object properties, there are a few things you need to do.

  • Ensure your root class is a POJO, and is not a List or Container class.

  • Annotate your List property definitions with:

    • @JacksonXmlElementWrapper: allows you to specify the XML element to use for wrapping
    • @JacksonXmlProperty: allows you to specify the XML local element name for a property
  • Keep your XML container element (@JacksonXmlElementWrapper) free from any attributes, as these can't be marshalled into a List object.

  • Ensure ObjectMapper strict mode is enabled, so that any mapping issues are not ignored.

The following example Groovy code and XML illustrate these design patterns.

package com.maguire.vo
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import java.util.*
class Person {
String firstName
String lastName
String email
Address currentAddress

@JacksonXmlElementWrapper(localName = "PreviousAddresses")
@JacksonXmlProperty(localName = "Address")
List<Address> previousAddresses
}
class Address {
String line1
String city
Integer postCode
String country
}

In this example, the XML repeating Address elements of the wrapper PreviousAddresses element are mapped into the Person class's previousAddresses field, and each child node Address will create an Address object and add it to previousAddresses.

<?xml version="1.0"?>
<AvokaSmartForm>
<Person>
<FirstName>John</FirstName>
<LastName>Smith</LastName>
<Email>[email protected]</Email/>
<CurrentAddress>
<Line1>12 Peterson Avenue</Line1>
<City>Hope Town</City>
<PostCode>4275</PostCode>
<Country/>
</CurrentAddress>
<PreviousAddresses>
<Address>
<Line1>30 Peace Street</Line1>
<City>Hope Town</City>
<PostCode>4276</PostCode>
<Country/>
</Address>
<Address>
<Line1>Unit 3/65 Dunkley Avenue</Line1>
<City>Melville</City>
<PostCode>5621</PostCode>
<Country>Australia<Country>
</Address>
</PreviousAddresses>
</Person>
<AvokaSmartForm>

Updating the XML Document

The following Groovy example stores a Person object in the Document using the XPath '/Person'. The ObjectMapper will set the appropriate root XPath automatically based on the document type.

import com.avoka.tm.util.*
import com.maguire.vo.*
Person person = new Person()
person.firstName = 'Peter'
new ObjectMapper()
.setObject(person)
.setFormXPath('/Person')
.update(param.appDoc)

Object Map Field Support

Support is also provided for Object Map fields. This feature is useful to map adhoc XML elements into a class object.

The following example adds a customProps Map field.

package com.maguire.vo
import java.util.*
class Person {
String firstName
String lastName
String emailAddress
Map<String, String> customProps
}

XML of Primary Applicant's customProps - could be properties such as account number or tax number.

<Person>
<CustomProps>
<accountNumber>12345</accountNumber>
<taxNumber>67890</taxNumber>
</CustomProps>
</Person>
import com.avoka.tm.util.*
import com.maguire.vo.*
Person person = new ObjectMapper()
.setDoc(param.appDoc)
.setFormXPath('/Person')
.create(Person.class)
String accountNumber = person.customProps.get('accountNumber')
// May put new property value and use update...

Transact Function Example

The function example below creates a new Applicant object graph from the application document (param.appDoc).

The example updates the applicant's address details, then writes the object graph back into the application document. Finally, in the FormFuncResult result, we turn on the option to return the form XML document back to the form. The JavaScript form application then updates its model with the new address details and displays them to the user.

package com.maguire.svc;
import com.avoka.tm.func.*
import com.avoka.tm.svc.*
import com.avoka.tm.util.*
import com.avoka.tm.vo.*
import com.maguire.vo.*
import javax.servlet.http.*
public class Controller {
// Injected at runtime
public Logger logger
FuncResult invoke(FuncParam param) {
// Marshal formData into applicant object VO graph
Person applicant = new ObjectMapper()
.setDoc(param.appDoc)
.setFormXPath("/Applicant")
.create(Person.class)
logger.info("Applicant: " + applicant)
// Add applicant address information
applicant.currentAddress = new Address()
applicant.currentAddress.line1 = "61 Main Street"
applicant.currentAddress.city = "Avalon"
applicant.currentAddress.postCode = "2107"
applicant.currentAddress.country = "Australia"
// Update formData with updated application object
new ObjectMapper()
.setObject(applicant)
.setFormXPath("/Applicant")
.update(param.appDoc)
FormFuncResult result = new FormFuncResult()
// Specify formData to be returned to form
result.includeFormData = true
return result
}
}

The XML response document for this example is shown below. Here, we have mapped the Person objects into the document's Applicant element.

<?xml version="1.0"?>
<AvokaSmartForm>
<Applicant>
<FirstName>John</FirstName>
<LastName>Smith</LastName>
<Email>[email protected]</Email/>
<CurrentAddress>
<Line1>61 Main Street</Line1>
<City>Avalon</City>
<PostCode>2107</PostCode>
<Country>Australia</Country>
</CurrentAddress>
<PreviousAddresses/>
</Applicant>
<AvokaSmartForm>