How to combine #JsonView with #JsonProperty? - json

I have a DTO class that should serve json via a spring-mvc #RestController.
I want to provide different version/views on the same object. Especially, there are fields that are only used in VERSION_1 of the api, and some only in VERSION_2.
Problem: I could add #JsonView for this, but my goal is also to rename those fields. Some fields should actually replace the same name from previous versions.
Example:
public class Person {
#JsonView(View.Version_1.class)
#JsonProperty("name")
private String name; //eg only the firstname
#JsonView(View.Version_2.class)
#JsonProperty("name")
private NameDTO namedto; //now changing to first+last name
static class NameDTO {
private String firstname;
private String lastname;
}
}
#RestController
public class MyServlet {
#GetMapping("/person/{id}")
#JsonView(View.Version_1.class)
public PersonDTO person1(int id) {
//...
}
#GetMapping("/person_new/{id}")
#JsonView(View.Version_2.class)
public PersonDTO person2(int id) {
//...
}
}
So, depending on the view/version, you would get the same json field firstname, but with different content.
In this example, using V1 would give:
{"name": "john"}
Whereas using V2 should result in:
{"name": {"firstname": "john", "lastname": "doe"}}
BUT not with he code above, as jackson complains:
com.fasterxml.jackson.databind.JsonMappingException: Conflicting
getter definitions for property "name".
Is that possible at all?

I found a way using:
https://github.com/jonpeterson/spring-webmvc-model-versioning
Basic idea is to add a custom VersionedModelConverter that is applied on #VersionedModelConverter annotated webservice response classes.
#Configuration
#Import(VersionedModelResponseBodyAdvice.class)
public class SpringMvcVersioningConfiguration {
//register in jackson. spring-boot automatically registers any module beans
#Bean
public Model versioningModel() {
return new VersioningModule();
}
}
#GetMapping
#VersionedResponseBody(defaultVersion = "2.0")
public Person person() {
}
#JsonVersionedModel(currentVersion = "3.0" toPastConverterClass = PersonConverter.class)
public class Person {
}
public class PersonConverter implements VersionedModelConverter {
#Override
public ObjectNode convert(ObjectNode modelData, String modelVersion, String targetModelVersion, JsonNodeFactory nodeFactory) {
Double modelv = Double.valueOf(modelVersion);
Double targetv = Double.valueOf(targetVersion);
//todo if-else based on model version
Object node = modelData.remove("fieldname");
//node.change...
modelData.set("fieldname_renamed", node);
}
}

Related

Is there a way to detect unmapped json properties when #JsonIgnoreProperties(ignoreUnknown = true) is set?

I need to detect which json fields are not mapped to the data model after PUT or POST requests.
For example:
If I post this:
{
"firstName": "test",
"lastName": "test 2",
"age": 25
}
and my model only have firstName and lastName, I want to list all unmapped fields, which in this example is "age" field.
Yes, that is possible using Jackson's annotation #JsonAnySetter
#JsonIgnoreProperties(ignoreUnknown = true)
public class DTO {
private String first;
private String last;
private Map<String, Object> unknown = new HashMap<>();
// getters/setters omitted
#JsonAnySetter
public void set(String name, Object value) {
unknown.put(name, value);
}
public Map<String, Object> getUnknown() {
return unknown;
}
}
Simple test:
#Test
public void testUnknown() throws Exception {
String json = "{\"first\":\"John\", \"last\":\"Doe\", \"age\":\"29\"}";
DTO dto = new ObjectMapper().readValue(json, DTO.class);
assertEquals(1, dto.getUnknown().size());
assertEquals("29", dto.getUnknown().get("age"));
}
If it's just about learning which properties are unmapped you may want to consider using this library: https://github.com/whiskeysierra/jackson-module-unknown-property
It logs unmapped properties for all mapped classes without a need to modify class itself.

how to add custom objectMapper for a specific spring rest controller

I have two controllers in my micro service both are POST and accepts Request body as JSON, one is working fine and another one's JSON input from some othet team and it is with root class name , so I need to write custom object mapper for this later controller, could you please guys help,
please find the codes below,
#RestController
#Slf4j
public class Controller2 {
#RequestMapping(value = "/some/update", method = RequestMethod.POST)
public String updateEmd(#RequestBody final UpdateEMDRequest updateEMDRequest) throws JsonProcessingException {
updateEMDRequest.getBookingReference()); // null now
return "success";
}
}
and the sample json is as follows,
{
"UpdateEMDRequest":{
"TransactionStatus":"SUCCESS",
"UniqueTransactionReference":"046060420",
"PreAuthReference":"040520420",
"BookingReference":"8PJ",
"CarrierCode":"AS",
"TransactionMode":"Batch",
"CallBackUrl":"www.test.com/op/update",
"Offers":[
{
"Offer":{
"traveler":{
"firstName":"AHONY",
"surname":"DNEN",
"EMD":[
"081820470"
]
}
}
}
]
}
}
UpdateEMDRequest,java
#JsonInclude(Include.NON_NULL)
public class UpdateEMDRequest {
#JsonProperty("UniqueTransactionReference")
private String uniqueTransactionReference;
#JsonProperty("TransactionStatus")
private String transactionStatus;
#JsonProperty("PreAuthReference")
private String preAuthReference;
#JsonProperty("BookingReference")
private String bookingReference;
#JsonProperty("CarrierCode")
private String carrierCode;
#JsonProperty("TransactionMode")
private String transactionMode;
#JsonProperty("CallBackUrl")
private String callBackUrl;
#JsonProperty("Offers")
private List<Offers> offers;
}
So this json is not parsed properly and updateEMDRequest's properties are null always.

Hide JSON fields in Jersey RESTful

The thing is that I want to hide the null elements from a RESTFul JSON response (if it's possible).
The REST controller retrieves the information from a Mongo database and because this elements doesn't exist there I would like to ignore them when they are null.
This is my REST Controller (exposed with Jersey):
#Stateless
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
#Path(PropertiesRestURIConstants.PROPERTIES)
#Produces(MediaType.APPLICATION_JSON)
#RequestScoped
public class GetPropertiesController {
#EJB(mappedName = PropertiesManagerRemote.MAPPED_NAME)
PropertiesManagerRemote propertiesManager;
#GET
#Path(PropertiesRestURIConstants.PROPERTIES_ALL)
public List<PropertyEntity> getAllProperties() throws DBLayerException {
return propertiesManager.getAllProperties();
}
...
...
...
}
This is my entity:
#Document(collection = "property")
public class PropertyEntity implements GenericEntity {
#Id
private String id;
private String propertyName;
private String propertyValue;
public PropertyEntity() {
}
public PropertyEntity(String propertyName, String propertyValue) {
this.propertyName = propertyName;
this.propertyValue = propertyValue;
}
...
...
...
}
And this is the result:
[{"id":"542c00c2ff5e0ba4ea58790d","propertyName":"property1","propertyValue":null},{"id":"542c00c2ff5e0ba4ea58790e","propertyName":"property2","propertyValue":null},{"id":"542c00c2ff5e0ba4ea58790f","propertyName":"property3","propertyValue":null}]
I use Spring Data for the persistence layer. I tried with JSONIgnore annotations and similar things, but nothing works for me.
Any help will be welcome.
Thanks in advance.
Try to annotate it this way:
#JsonInclude(Include.NON_EMPTY)
public class PropertyEntity implements GenericEntity {

Jackson with JAXB - abstract types instantiation

I'm facing problem with Jackson's ObjectMapper using JAXB annotations. To be concrete, I'm having collection with interface generic information and although I can deserialize input from XML, it is not possible with Jackson (using JAXB introspector). Maybe I'm just missing some configuration property or JAXB annotation? The problem is that "abstract types can only be instantiated with additional type information", I thought #XmlElementRef (or #XmlElement) with type information will handle this problem, but obviosly it does not.
Please note, that I want to stay only with JAXB annotations if possible.
E.g. using #JsonTypeInfo or #JsonDeserialize would be the last thing to do.
IEntry.java:
#XmlSeeAlso(Entry.class)
public interface IEntry {
String getValue();
}
Entry.java:
#XmlRootElement(name = "entry")
public class Entry implements IEntry {
#XmlElement(name = "value")
String value;
public Entry() {
}
public Entry(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
Aggregator.java:
#XmlRootElement(name = "aggregator")
public class Aggregator {
#XmlElementRef(type = Entry.class)
private Set<IEntry> entries;
public Aggregator() {
}
public Aggregator(Set<IEntry> entries) {
this.entries = entries;
}
public Set<IEntry> getEntries() {
return entries;
}
}
Test method:
#Test
public void testSerialization() throws Exception {
ObjectMapper om = new ObjectMapper();
AnnotationIntrospector intr = new JaxbAnnotationIntrospector();
om.getDeserializationConfig().withAnnotationIntrospector(intr);
String json = "{\"entries\":[{\"value\":\"X\"},{\"value\":\"Y\"},{\"value\":\"Z\"}]}\";\n}";
Aggregator agr = om.readValue(json, Aggregator.class);
}
Thanks for all response
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
I am not sure if Jackson supports this use case or not, but you appear to be using #XmlElementRef incorrectly. When you use #XmlElementRef the root element name associated with the class is used to determine the instance to be instantiated. If your example the node entries does not match the #XmlRootElement(name="entry") annotation.
You could try one of the following options (they all work with MOXy's JSON binding, see: http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html):
OPTION 1 - Change #XMLRootElement on Entry
#XmlRootElement(name = "entries")
public class Entry implements IEntry {
#XmlElement(name = "value")
String value;
public Entry() {
}
public Entry(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
OPTION #2 - Change the JSON Document
{"entry":[{"value":"X"},{"value":"Y"},{"value":"Z"}]}}
OPTION #3 - Use #XMLElement instead of #XMLElementRef
If you use the #XmlElement annotation you can specify on the field/property what the node name should be instead of relying on the #XmlRootElement annotation. Also if you annotate the fields you should specify #XmlAccessorType(XmlAccessType.FIELD) at the type level.
import java.util.Set;
import javax.xml.bind.annotation.*;
#XmlRootElement(name = "aggregator")
#XmlAccessorType(XmlAccessType.FIELD)
public class Aggregator {
#XmlElement(type = Entry.class)
private Set<IEntry> entries;
public Aggregator() {
}
public Aggregator(Set<IEntry> entries) {
this.entries = entries;
}
public Set<IEntry> getEntries() {
return entries;
}
}
For More Information
http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-substitution.html
http://blog.bdoughan.com/2011/05/jaxb-and-interface-fronted-models.html
import org.codehaus.jackson.map.annotate.JsonDeserialize;
#JsonDeserialize(as = Entry.class)
public interface IEntry {
String getValue();
}

Jackson deserialization - with contained ArrayList<T>

Good day,
I am currently integration attempting to consume a REST service that produces JSON (written in .NET) using Jackson (with Jersey). The JSON consists of a possible error message and an array of objects. Below is a sample of the JSON returned as produced by Jersey's logging filter:
{
"error":null,
"object":"[{\"Id\":16,\"Class\":\"ReportType\",\"ClassID\":\"4\",\"ListItemParent_ID\":4,\"Item\":\"Pothole\",\"Description\":\"Pothole\",\"Sequence\":1,\"LastEditDate\":null,\"LastEditor\":null,\"ItemStatus\":\"Active\",\"ItemColor\":\"#00AF64\"}]"
}
I have two classes to represent the type (the outer ListResponse):
public class ListResponse {
public String error;
public ArrayList<ListItem> object;
public ListResponse() {
}
}
and (the inner ListItem):
public class ListItem {
#JsonProperty("Id")
public int id;
#JsonProperty("Class")
public String classType;
#JsonProperty("ClassID")
public String classId;
#JsonProperty("ListItemParent_ID")
public int parentId;
#JsonProperty("Item")
public String item;
#JsonProperty("Description")
public String description;
#JsonAnySetter
public void handleUnknown(String key, Object value) {}
public ListItem() {
}
}
The class that invokes and returns the JSON looks like this:
public class CitizenPlusService {
private Client client = null;
private WebResource service = null;
public CitizenPlusService() {
initializeService("http://localhost:59105/PlusService/");
}
private void initializeService(String baseURI) {
// Use the default client configuration.
ClientConfig clientConfig = new DefaultClientConfig();
clientConfig.getClasses().add(JacksonJsonProvider.class);
client = Client.create(clientConfig);
// Add a logging filter to track communication between server and client.
client.addFilter(new LoggingFilter());
// Add the base URI
service = client.resource(UriBuilder.fromUri(baseURI).build());
}
public ListResponse getListItems(String id) throws Exception
{
ListResponse response = service.path("GetListItems").path(id).accept(MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_XML_TYPE).get(ListResponse.class);
return response;
}
}
The important call here is the getListItems method. Running the code in a test harness, produces the following:
org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of VALUE_STRING token
at [Source: java.io.StringReader#49497eb8; line: 1, column: 14] (through reference chain: citizenplus.types.ListResponse["object"])
Please assist.
Regards,
Carl-Peter Meyer
You may be missing a #JsonDeserialize attribute as the type information does get lost in generics at run-time. Also you should avoid using concrete classes for collections if you can.
public class ListResponse {
public String error;
#JsonDeserialize(as=ArrayList.class, contentAs=ListItem.class)
public List<ListItem> object;
}
Your problem is that the 'object' property value is a String and not an array! The string contains a JSON array but Jackson expects a native array (without the wrapping quotes).
I had the same problem and I created a custom deserializer, which will deserialize a string value to a generic collection of the desired type:
public class JsonCollectionDeserializer extends StdDeserializer<Object> implements ContextualDeserializer {
private final BeanProperty property;
/**
* Default constructor needed by Jackson to be able to call 'createContextual'.
* Beware, that the object created here will cause a NPE when used for deserializing!
*/
public JsonCollectionDeserializer() {
super(Collection.class);
this.property = null;
}
/**
* Constructor for the actual object to be used for deserializing.
*
* #param property this is the property/field which is to be serialized
*/
private JsonCollectionDeserializer(BeanProperty property) {
super(property.getType());
this.property = property;
}
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
return new JsonCollectionDeserializer(property);
}
#Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
switch (jp.getCurrentToken()) {
case VALUE_STRING:
// value is a string but we want it to be something else: unescape the string and convert it
return JacksonUtil.MAPPER.readValue(StringUtil.unescapeXml(jp.getText()), property.getType());
default:
// continue as normal: find the correct deserializer for the type and call it
return ctxt.findContextualValueDeserializer(property.getType(), property).deserialize(jp, ctxt);
}
}
}
Note that this deserializer will also work if the value actually is an array and not a string, because it delegates the actual deserialization accordingly.
In your example you would now have to annotate your collection field like so:
public class ListResponse {
public String error;
#JsonDeserialize(using = JsonCollectionDeserializer.class)
public ArrayList<ListItem> object;
public ListResponse() {}
}
And that should be it.
Note: JacksonUtil and StringUtil are custom classes, but you can easily replace them. For example by using new ObjectMapper() and org.apache.commons.lang3.StringEscapeUtils.
The register subTypes works!
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
public interface Geometry {
}
public class Point implements Geometry{
private String type="Point";
....
}
public class Polygon implements Geometry{
private String type="Polygon";
....
}
public class LineString implements Geometry{
private String type="LineString";
....
}
GeoJson geojson= null;
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.registerSubtypes(Polygon.class,LineString.class,Point.class);
try {
geojson=mapper.readValue(source, GeoJson.class);
} catch (IOException e) {
e.printStackTrace();
}