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.
- Create a Groovy object graph from an XML document.
- 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.
Property | Description |
---|---|
strictMode | boolean (Default: true )Whether a missing Object field for a Document element will throw an exception. 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. |
mappingStrategy | ObjectMapper.MappingStrategy (Default: CamelUpperCase )The XML tag name to object mapping strategy. Must be one of the following.
<FirstName> maps to the object class's firstName field. |
dateFormat | java.text.DateFormat (Default: "yyyy-MM-dd" )The datetime marshalling format. |
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
orContainer
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 aList
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>