Spring MVC binding for using Jackson Model Versioning Module.
Note: This example is using Groovy for brevity, but it is not required.
Car data v1
{
"model": "honda:civic",
"year": 2016,
"new": "true"
}
Car data v2
{
"make": "honda",
"model": "civic",
"year": 2016,
"new": "true"
}
Car data v3
{
"make": "honda",
"model": "civic",
"year": 2016,
"used": false
}
Create a POJO for the newest version of the data. Using the Jackson Model Versioning Module, annotate the model with the current version and specify the converter class to use when deserializing from a potentially old version of the model to the current version and the converter class to use when serializing from the current version to a potentially old version of the model. Also add a field (or getter/setter methods) that specify the version that the model to be serialized to.
@JsonVersionedModel(currentVersion = '3',
toCurrentConverterClass = ToCurrentCarConverter,
toPastConverterClass = ToPastCarConverter)
class Car {
String make
String model
int year
boolean used
@JsonSerializeToVersion
String serializeToVersion
}
Create the "up" converter and provide logic for how old versions should be converted to the current version.
class ToCurrentCarConverter implements VersionedModelConverter {
@Override
def ObjectNode convert(ObjectNode modelData, String modelVersion,
String targetModelVersion, JsonNodeFactory nodeFactory) {
// model version is an int
def version = modelVersion as int
// version 1 had a single 'model' field that combined 'make' and 'model' with a colon delimiter
if(version <= 1) {
def makeAndModel = modelData.get('model').asText().split(':')
modelData.put('make', makeAndModel[0])
modelData.put('model', makeAndModel[1])
}
// version 1-2 had a 'new' text field instead of a boolean 'used' field
if(version <= 2)
modelData.put('used', !Boolean.parseBoolean(modelData.remove('new').asText()))
}
}
Create the "down" converter and provide logic for how the current version should be converted to an old version.
class ToPastCarConverter implements VersionedModelConverter {
@Override
def ObjectNode convert(ObjectNode modelData, String modelVersion,
String targetModelVersion, JsonNodeFactory nodeFactory) {
// model version is an int
def version = modelVersion as int
def targetVersion = targetModelVersion as int
// version 1 had a single 'model' field that combined 'make' and 'model' with a colon delimiter
if(targetVersion <= 1 && version > 1)
modelData.put('model', "${modelData.remove('make').asText()}:${modelData.get('model').asText()}")
// version 1-2 had a 'new' text field instead of a boolean 'used' field
if(targetVersion <= 2 && version > 2)
modelData.put('new', !modelData.remove('used').asBoolean() as String)
}
}
Configure the Jackson ObjectMapper used by Spring MVC to use the Jackson Model Versioning Module and import VersionedModelResponseBodyAdvice.
@Configuration
@Import(VersionedModelResponseBodyAdvice)
class SpringMvcVersioningConfiguration {
@Bean
Jackson2ObjectMapperBuilder objectMapperBuilder() {
return Jackson2ObjectMapperBuilder.json().modulesToInstall(new VersioningModule())
}
}
Create a REST endpoint
@RequestMapping(method = RequestMethod.POST,
path = '/',
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
@VersionedResponseBody(defaultVersion = '2',
headerName = 'Model-Version',
queryParamName = 'modelVersion')
Car createCar(@RequestBody Car car) {
return carRepository.insert(car)
}
All that's left is to test it out.
def restTemplate = new RestTemplate(
messageConverters: [
new MappingJackson2HttpMessageConverter(new ObjectMapper().registerModule(new VersioningModule()))
]
)
// POST version 1 JSON and request version 2 JSON response via URL query param
println restTemplate.postForObject(
'http://localhost:8080/?modelVersion=2',
'{"model": "honda:civic", "year": 2016, "new": "true", "modelVersion": "1"],
String
)
// prints '{"make":"honda","model":"civic","year":2016,"new":"true","modelVersion":"2"}'
// POST version 1 JSON and request version 2 JSON response via HTTP header
println restTemplate.exchange(
'http://localhost:8080/',
HttpMethod.POST,
new HttpEntity<String>(
'{"model": "honda:civic", "year": 2016, "new": "true", "modelVersion": "1"]',
new LinkedMultiValueMap<String, String>('Model-Version': '2')
),
String
)
// prints '{"make":"honda","model":"civic","year":2016,"new":"true","modelVersion":"2"}'
See the tests under src/test/groovy
for more.
- Requires Java 6 or higher
- Requires Spring 4.2 or higher (tested with Spring 4.2 - 4.3)
- Requires Jackson 2.2 or higher (uses a version of Jackson Model Versioning Module which is tested with Jackson 2.2 - 2.8).
dependencies {
compile 'com.github.jonpeterson:spring-webmvc-model-versioning:1.0.0'
}
<dependency>
<groupId>com.github.jonpeterson</groupId>
<artifactId>spring-webmvc-model-versioning</artifactId>
<version>1.0.0</version>
</dependency>