Convert JSON to Java Object, how to parse BadgerFish convention with Jackson - java

Using an API I receive JSON like this (which are now saved to file):
[{
"LEI": {
"$": "549300Q82NZ9NYNMZT63"
},
"Entity": {
"LegalName": {
"$": "United Nerds in Collaboration of Random Nerdiness AB"
},
"LegalAddress": {
"Line1": {
"$": "BOX 155"
},
"City": {
"$": "Alingsas"
},
"Region": {
"$": "SE-O"
},
"Country": {
"$": "SE"
},
"PostalCode": {
"$": "44123"
}
},
"HeadquartersAddress": {
"Line1": {
"$": "BOX 155"
},
"City": {
"$": "Alingsas"
},
"Region": {
"$": "SE-O"
},
"Country": {
"$": "SE"
},
"PostalCode": {
"$": "44123"
}
},
"BusinessRegisterEntityID": {
"#register": "SE001",
"$": "5568557184"
},
"LegalJurisdiction": {
"$": "SE"
},
"LegalForm": {
"$": "PRIVATA AKTIEBOLAG"
},
"EntityStatus": {
"$": "ACTIVE"
}
},
"Registration": {
"InitialRegistrationDate": {
"$": "2016-06-23T01:48:45.025Z"
},
"LastUpdateDate": {
"$": "2016-06-23T01:48:44.945Z"
},
"RegistrationStatus": {
"$": "ISSUED"
},
"NextRenewalDate": {
"$": "2017-06-21T06:32:03.821Z"
},
"ManagingLOU": {
"$": "EVK05KS7XY1DEII3R011"
},
"ValidationSources": {
"$": "PARTIALLY_CORROBORATED"
}
}
}]
I would like to get Java Object out of these. I have already created the Java Objects out of an xsd file provided. The code I'm running is:
public static void toJava() {
ObjectMapper mapper = new ObjectMapper();
try {
File json = new File("C:\\temp\\JSON.json");
LEIRecordType[] type = mapper.readValue(json, LEIRecordType[].class);
} catch (JsonEOFException ex) {
ex.printStackTrace();
} catch (JsonMappingException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
}
Which creates these Exceptions:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "LEI" (class org.leiroc.data.schema.leidata._2014.LEIRecordType), not marked as ignorable (5 known properties: "lei", "registration", "entity", "nextVersion", "extension"])
at [Source: (File); line: 3, column: 14] (through reference chain: java.lang.Object[][0]->org.leiroc.data.schema.leidata._2014.LEIRecordType["LEI"])
at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:60)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:822)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1152)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1567)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1545)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:293)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:195)
at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:21)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4001)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2890)
at Test.JSONParser.toJava(JSONParser.java:38)
at Test.JSONParser.main(JSONParser.java:29)
LEIRecordType looks like this:
package org.leiroc.data.schema.leidata._2014;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "LEIRecordType", propOrder = {"lei", "entity", "registration", "nextVersion", "extension"})
public class LEIRecordType {
#XmlElement(name = "LEI", required = true)
protected String lei;
#XmlElement(name = "Entity", required = true)
protected EntityType entity;
#XmlElement(name = "Registration", required = true)
protected RegistrationType registration;
#XmlElement(name = "NextVersion")
protected LEIRecordNextVersionType nextVersion;
#XmlElement(name = "Extension")
protected ExtensionType extension;
public String getLEI() {
return this.lei;
}
public void setLEI(String paramString) {
this.lei = paramString;
}
public EntityType getEntity() {
return this.entity;
}
public void setEntity(EntityType paramEntityType) {
this.entity = paramEntityType;
}
public RegistrationType getRegistration() {
return this.registration;
}
public void setRegistration(RegistrationType paramRegistrationType) {
this.registration = paramRegistrationType;
}
public LEIRecordNextVersionType getNextVersion() {
return this.nextVersion;
}
public void setNextVersion(LEIRecordNextVersionType paramLEIRecordNextVersionType) {
this.nextVersion = paramLEIRecordNextVersionType;
}
public ExtensionType getExtension() {
return this.extension;
}
public void setExtension(ExtensionType paramExtensionType) {
this.extension = paramExtensionType;
}
}
I understand that the problem is that jackson is locking for an Java Object called LEI, with an variable called "$". But there is none. The organisations help service says:
"The "$" object always reproduces the simple content (i.e. not the attributes, child nodes etc.) of the corresponding XML element.
The "$" object should always be typed as a JSON string where applicable."
But as I understand this is not JSON standard.
My question is: Is there any way to get jackson to parse this as LEI = "549300Q82NZ9NYNMZT63" etc. instead of and object LEI with an variable "$"?
Have been stuck on this for the better part of a day.
#UPDATE
This JSON format is apparently called "The BadgerFish convention", accoring to customer services.

As the $ object is always a String, you can create a custom deserializer for Strings that handles the BadgerFish wrapper objects.
This deserializer checks if there is a BadgerFish wrapper object around a String value and unwraps it. Normal String values are deserialized as usual.
public class BadgerFishDeserializer extends StdDeserializer<String> {
private static final long serialVersionUID = 1L;
private static final SerializedString BADGER_FISH_FIELD_NAME = new SerializedString("$");
public BadgerFishDeserializer() {
super(String.class);
}
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// do we have a wrapper object?
if (jp.isExpectedStartObjectToken()) {
// check if first field name is equal to '$'
if (!jp.nextFieldName(BADGER_FISH_FIELD_NAME)) {
ctxt.reportInputMismatch(String.class, "Expected BadgerFish field name '$', but got '%s'", jp.getCurrentName());
}
jp.nextValue(); // proceed to the actual value
String value = jp.getValueAsString(); // read value as string
jp.nextToken(); // consume END_OBJECT of wrapper object
return value;
}
// else: just return string value
return jp.getValueAsString();
}
}
Finally register the module on your Jackson ObjectMapper instance:
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new BadgerFishDeserializer());
mapper.registerModule(module);
Note: If you only want some properties to be unwrapped, you could create a custom annotation and use a BeanDeserializerModifier to check for the annotation and then provide a deserializer that handles the wrapper objects.
Some food for thought:
Create annotation
Modify the deserializer to always expect wrapper objects (fail on plain Strings)
Create a DeserializerModifier
Register the DeserializerModifier on ObjectMapper
The difficult part:
public class BadgerFishDeserializerModifier extends BeanDeserializerModifier {
#Override
public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {
Iterator<SettableBeanProperty> props = builder.getProperties();
while (props.hasNext()) {
SettableBeanProperty prop = props.next();
if (prop.getAnnotation(MyAnnotation.class) != null) {
builder.addOrReplaceProperty(prop.withValueDeserializer(new BadgerFishDeserializer()), true);
}
}
return builder;
}
}

This has been very helpful! I ended up having to do a special deserializer for both String and XMLGregorianCalendar aswell. But the problem does not stop there. BadgerFish takes the #XMLAttributes from the generated classes and sets them as #value instead of "value". As for example:
"BusinessRegisterEntityID": {
"#register": "SE001",
"$": "5568557184"
}
So is their any way to also custom the field name back to the original? Currently I now get this exception:
Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "#register" (class leigen.BusinessRegisterEntityIDType), not marked as ignorable (2 known properties: "value", "register"])
at [Source: (File); line: 44, column: 27] (through reference chain: java.lang.Object[][0]->leigen.LEIRecordType["Entity"]->leigen.EntityType["BusinessRegisterEntityID"]->leigen.BusinessRegisterEntityIDType["#register"])
at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:60)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:822)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1152)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1567)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1545)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:293)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:136)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:287)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:136)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:287)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:195)
at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:21)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4001)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2890)
at Test.JSONParser.toJava(JSONParser.java:47)
at Test.JSONParser.main(JSONParser.java:28)
I'm able to bypass this by putting a "ignore" (DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES )in the config like in my now current code here:
public static LEIRecordType[] toJava(File json) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new BadgerFishStringDeserializer());
module.addDeserializer(XMLGregorianCalendar.class, new BadgerFishXMLGregorianCalendarDeserializer());
module.addDeserializer(NameType.class, new BadgerFishNameTypeDeserializer());
mapper.registerModule(module);
mapper.registerModule(new JaxbAnnotationModule()); // To be able to read JAXB annotations.
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper.readValue(json, LEIRecordType[].class);
}
But this only sets the values starting with # as null. Is there another way to solve this? Otherwise I would have to write a custom deserializer for all my generated classes, which is about 25 (and there could always be more in the next version). I have already done one for NameType, but for other example more are needed.

I think the clear solution is using #JsonProperty("$"):
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
List<LEIModel> leiList = Arrays.asList(mapper.readValue(response.toString(), LEIModel[].class));
The LEIModel class:
public class LEIModel {
#JsonProperty("Entity")
private Entity entity;
public Entity getEntity() {
return entity;
}
public void setEntity(Entity entity) {
this.entity = entity;
}
}
The Entity class:
public class Entity {
#JsonProperty("LegalName")
private LegalName legalName;
public LegalName getLegalName() {
return legalName;
}
public void setLegalName(LegalName legalName) {
this.legalName = legalName;
}
}
The LegalName class:
public class LegalName {
#JsonProperty("$")
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

Related

How ignore empty json objects like "car":{}, that cause empty pojos after deserialisation with jackson

I have a rest service that consume json from an Angular UI and also from other rest clients. The data based on a complex structure of entities ~50 that are stored in a database with ~50 tables. The problem are the optional OneToOne relations, because Angular send the optional objects as empty definitions like "car": {},. The spring data repository saves them as empty entries and I got a Json response like "car": {"id": 545234, "version": 0} back. I found no Jackson annotation to ignore empty objects, only empty or null properties.
The Employee Entity has the following form:
#Entity
public class Employee {
#Id
#GeneratedValue
private Long id;
#Version
private Long version;
private String name;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "car_id")
#JsonManagedReference
private Car car;
.
. Desk, getters and setters
.
}
and the other side of the OneToOne Reference
#Entity
public class Car{
#Id
#GeneratedValue
private Long id;
#Version
private Long version;
private String name;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "employee")
#JsonBackReference
private Employee employee;
.
. getters and setters
.
}
For example, I send this to my service as post operation
{
"name": "ACME",
.
.
.
"employee": {
"name": "worker 1",
"car": {},
"desk": {
floor: 3,
number: 4,
phone: 444
}
.
.
.
},
"addresses": [],
"building": {},
.
.
.
}
and I got as response the saved data
{
"id": 34534,
"version": 0,
"name": "ACME",
.
.
.
"employee": {
"id": 34535,
"version":0,
"name": "worker 1",
"car": {"id": 34536, "version": 0},
"desk": {
"id": 34538,
"version":0,
"floor": 3,
"number": 4,
"phone": 444
}
.
.
.
},
"addresses": [],
"building": {"id": 34539, "version": 0},
.
.
.
}
As seen in the response I got empty table rows with an id, a version, many null values and empty strings, because when I save (persist) the main deserialized company class, the other entity are also saved, because the are annotated as cascading.
I found many examples like Do not include empty object to Jackson , with a concrete pojo and a concrete deserializer that are working, but every entity needs his own Deserializer. This causes many work for the current entities and the new ones in the future (only the optional entities).
I tried the folowing, I write a BeanDeserializerModifier and try to wrap an own deserializer over the standard beandeserializer:
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public List<BeanPropertyDefinition> updateProperties(DeserializationConfig config,
BeanDescription beanDesc,
List<BeanPropertyDefinition> propDefs) {
logger.debug("update properties, beandesc: {}", beanDesc.getBeanClass().getSimpleName());
return super.updateProperties(config, beanDesc, propDefs);
}
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription beanDesc,
JsonDeserializer<?> deserializer) {
logger.debug("modify deserializer {}",beanDesc.getBeanClass().getSimpleName());
// This fails:
// return new DeserializationWrapper(deserializer, beanDesc);
return deserializer; // This works, but it is the standard behavior
}
});
And here is the wrapper (and the mistake):
public class DeserializationWrapper extends JsonDeserializer<Object> {
private static final Logger logger = LoggerFactory.getLogger( DeserializationWrapper.class );
private final JsonDeserializer<?> deserializer;
private final BeanDescription beanDesc;
public DeserializationWrapper(JsonDeserializer<?> deserializer, BeanDescription beanDesc) {
this.deserializer = deserializer;
this.beanDesc = beanDesc;
}
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
logger.debug("deserialize in wrapper {} ",beanDesc.getBeanClass().getSimpleName());
final Object deserialized = deserializer.deserialize(p, ctxt);
ObjectCodec codec = p.getCodec();
JsonNode node = codec.readTree(p);
// some logig that not work
// here. The Idea is to detect with the json parser that the node is empty.
// If it is empty I will return null here and not the deserialized pojo
return deserialized;
}
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt, Object intoValue) throws IOException {
logger.debug("deserializer - method 2");
intoValue = deserializer.deserialize(p, ctxt);
return intoValue;
}
#Override
public boolean isCachable() {
return deserializer.isCachable();
}
.
. I try to wrap the calls to the deserializer
.
The Deserialization Wrapper does not work and crash after the first call with an exception com.fasterxml.jackson.databind.exc.MismatchedInputException: No _valueDeserializer assigned at [Source: (PushbackInputStream); line: 2, column: 11] (through reference chain: ... Company["name"])
My question: is there a way to extend the behavior of the working standard deserializer in the way, that the deserializer detect while parsing, that the current jsonNode is empty and return null instead the empty class instance? Perhaps my Idea is wrong and there is a completely other solution?
Solving it on the Angular UI side is no option. We use Jackson 2.9.5.
Using BeanDeserializerModifier with custom deserialiser is a good idea. You need to improve only a deserialiser implementation. In your example problem is with these lines:
final Object deserialized = deserializer.deserialize(p, ctxt); //1.
ObjectCodec codec = p.getCodec(); //2.
JsonNode node = codec.readTree(p); //3.
Line 1. reads JSON Object. In lines 2. and 3. you want to create a JsonNode but empty JSON Object was already read in line 1.. Next two lines will try to read rest of payload as JsonNode.
Jackson by default uses BeanDeserializer to deserialise regular POJO classes. We can extend this class and provide our own implementation. In version 2.10.1 deserialise method looks like this:
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
// common case first
if (p.isExpectedStartObjectToken()) {
if (_vanillaProcessing) {
return vanillaDeserialize(p, ctxt, p.nextToken());
}
// 23-Sep-2015, tatu: This is wrong at some many levels, but for now... it is
// what it is, including "expected behavior".
p.nextToken();
if (_objectIdReader != null) {
return deserializeWithObjectId(p, ctxt);
}
return deserializeFromObject(p, ctxt);
}
return _deserializeOther(p, ctxt, p.getCurrentToken());
}
In most cases, where there is no special treatment needed vanillaDeserialize method will be invoked. Let's look on it:
private final Object vanillaDeserialize(JsonParser p, DeserializationContext ctxt, JsonToken t) throws IOException {
final Object bean = _valueInstantiator.createUsingDefault(ctxt);
// [databind#631]: Assign current value, to be accessible by custom serializers
p.setCurrentValue(bean);
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
String propName = p.getCurrentName();
do {
p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) { // normal case
try {
prop.deserializeAndSet(p, ctxt, bean);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
continue;
}
handleUnknownVanilla(p, ctxt, bean, propName);
} while ((propName = p.nextFieldName()) != null);
}
return bean;
}
As you can see, it does almost everything what we want, except it creates new object even for empty JSON Object. It checks whether field exists just after it creates new objects. One line too far. Unfortunately, this method is private and we can not override it. Let's copy it to our class and modify a little bit:
class EmptyObjectIsNullBeanDeserializer extends BeanDeserializer {
EmptyObjectIsNullBeanDeserializer(BeanDeserializerBase src) {
super(src);
}
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
if (_vanillaProcessing) {
return vanillaDeserialize(p, ctxt);
}
return super.deserialize(p, ctxt);
}
private Object vanillaDeserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
p.nextToken();
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
final Object bean = _valueInstantiator.createUsingDefault(ctxt);
// [databind#631]: Assign current value, to be accessible by custom serializers
p.setCurrentValue(bean);
String propName = p.getCurrentName();
do {
p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) { // normal case
try {
prop.deserializeAndSet(p, ctxt, bean);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
continue;
}
handleUnknownVanilla(p, ctxt, bean, propName);
} while ((propName = p.nextFieldName()) != null);
return bean;
}
return null;
}
}
You can register it like below:
class EmptyObjectIsNullBeanDeserializerModifier extends BeanDeserializerModifier {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (beanDesc.getBeanClass() == Car.class) { //TODO: change this condition
return new EmptyObjectIsNullBeanDeserializer((BeanDeserializerBase) deserializer);
}
return super.modifyDeserializer(config, beanDesc, deserializer);
}
}
Simple POC:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonTokenId;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.module.SimpleModule;
import lombok.Data;
import java.io.File;
import java.io.IOException;
public class EmptyObjectIsNullApp {
public static void main(String[] args) throws IOException {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
SimpleModule emptyObjectIsNullModule = new SimpleModule();
emptyObjectIsNullModule.setDeserializerModifier(new EmptyObjectIsNullBeanDeserializerModifier());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(emptyObjectIsNullModule);
Wrapper wrapper = mapper.readValue(jsonFile, Wrapper.class);
System.out.println(wrapper);
}
}
#Data
class Wrapper {
private Car car;
}
#Data
class Car {
private int id;
}
For "empty object" JSON payload:
{
"car": {}
}
Above code prints:
Wrapper(car=null)
For JSON payload with some fields:
{
"car": {
"id": 1
}
}
Above code prints:
Wrapper(car=Car(id=1))

Jackson deserialization bellow JSON property

I want to fetchMultiple(ParameterizedTypeReference<List<T>> responseType) for a given List<T>, in this case, I want to get directly a List<Account> but I am getting an error because the list of accounts is encapsulated in another object, as shown below:
{
"accounts": [
{
"accountUid": "c75deb59-5d52-4a23-af7b-fce29927ce9d",
"defaultCategory": "b4189da5-7688-42d0-86e3-14ae9031e01d",
"currency": "GBP",
"createdAt": "2020-08-05T16:50:50.536Z"
}
]
}
There is some Jackson annotation to filter this somehow in order to be processed like this:
[
{
"accountUid": "c75deb59-5d52-4a23-af7b-fce29927ce9d",
"defaultCategory": "b4189da5-7688-42d0-86e3-14ae9031e01d",
"currency": "GBP",
"createdAt": "2020-08-05T16:50:50.536Z"
}
]
POJO
#Data
public class Account {
private String accountUid;
private String defaultCategory;
private String currency;
private String createdAt;
}
RestRequestTemplate.java
public List<T> fetchMultiple(ParameterizedTypeReference<List<T>> responseType) {
return new RestTemplate().exchange(this.url, this.httpMethod, this.request, responseType).getBody();
}
AccountsServiceImpl.java
public List<Account> getAccounts() {
RestRequestTemplate restRequestTemplate = new RestRequestTemplate(GET_ACCOUNTS, HttpMethod.GET, Collections.EMPTY_MAP);
return restRequestTemplate.fetchMultiple(new ParameterizedTypeReference<List<Account>>() {});
}
There is indeed an annotation to ignore the root object. It is called #JsonUnwrapped. Annotate your method with that annotation and your json should be without the root object.

Is Jackson Serialization of this string possible without custom serializer?

I want to serialize a JSON-String I receive as a POJO, for further usage in my code, but I am struggling to get it working without writing a custom serializer.
I would prefer as solution without writing a custom serializer, but if that is the only possible way I will write one.
Additionally I believe the data I receive is a weird JSON since the list I request is not sent as list using [] but rather as a object using {}.
I receive the following list/object (shortened):
{
"results": {
"ALL": {
"currencyName": "Albanian Lek",
"currencySymbol": "Lek",
"id": "ALL"
},
"XCD": {
"currencyName": "East Caribbean Dollar",
"currencySymbol": "$",
"id": "XCD"
},
"EUR": {
"currencyName": "Euro",
"currencySymbol": "â?¬",
"id": "EUR"
},
"BBD": {
"currencyName": "Barbadian Dollar",
"currencySymbol": "$",
"id": "BBD"
},
"BTN": {
"currencyName": "Bhutanese Ngultrum",
"id": "BTN"
},
"BND": {
"currencyName": "Brunei Dollar",
"currencySymbol": "$",
"id": "BND"
}
}
}
I created my first POJO for the inner object like this:
public class CurrencyDTO implements Serializable {
private String currencyName;
private String currencySymbol;
private String currencyId;
#JsonCreator
public CurrencyDTO( #JsonProperty( "currencyName" ) String currencyName, #JsonProperty( "currencySymbol" ) String currencySymbol,
#JsonProperty( "id" ) String currencyId )
{
this.currencyId = currencyId;
this.currencyName = currencyName;
this.currencySymbol = currencySymbol;
}
}
which itself is fine. Now I wrote another POJO as a wrapper for the data a layer above which looks like this:
public class CurrencyListDTO implements Serializable {
private List<Map<String, CurrencyDTO>> results;
public CurrencyListDTO()
{
}
}
Adding the annotations #JsonAnySetter or using the #JsonCreator didn't help either, so I removed them again and now I am wondering which little trick could enable the correct serialization of the json.
My Exception is the following:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.ArrayList` out of START_OBJECT token
at [Source: (String)"{"results":{"ALL":{"currencyName":"Albanian Lek","currencySymbol":"Lek","id":"ALL"},"XCD":{"currencyName":"East Caribbean Dollar","currencySymbol":"$","id":"XCD"},"EUR":{"currencyName":"Euro","currencySymbol":"â?¬","id":"EUR"},"BBD":{"currencyName":"Barbadian Dollar","currencySymbol":"$","id":"BBD"},"BTN":{"currencyName":"Bhutanese Ngultrum","id":"BTN"},"BND":{"currencyName":"Brunei Dollar","currencySymbol":"$","id":"BND"},"XAF":{"currencyName":"Central African CFA Franc","id":"XAF"},"CUP":{"cur"[truncated 10515 chars]; line: 1, column: 12] (through reference chain: com.nico.Banking.api.data.dto.CurrencyListDTO["results"])
You should change your CurrencyListDTO to:
public class CurrencyListDTO {
private Map<String, CurrencyDTO> results;
// getters and setters
}
Because the results field in the response object is another object with the currencyId as key and no array.
You then can create your list of currencies like this:
ObjectMapper mapper = new ObjectMapper();
CurrencyListDTO result = mapper.readValue(json, CurrencyListDTO.class);
List<CurrencyDTO> currencies = new ArrayList<>(result.getResults().values());
Your CurrencyListDTO should look like below. results property is a JSON Object which should be mapped directly to Map. You can convert it to Collection using keySet or values methods.
class CurrencyListDTO implements Serializable {
private Map<String, CurrencyDTO> results;
public Map<String, CurrencyDTO> getResults() {
return results;
}
public void setResults(Map<String, CurrencyDTO> results) {
this.results = results;
}
#Override
public String toString() {
return "CurrencyListDTO{" +
"results=" + results +
'}';
}
}

How to create this JSONObject using java?

How do I create the following json using java classes and lombok's builder?
I used some json to pojo tool and created 2 classes: Entry.java and Testplan.java, added a method to convert String to json and managed to get a json object: {"suite_id":99,"name":"Some random name"}
I don't understand how to create one that would look like this:
{
"name": "System test",
"entries": [
{
"suite_id": 1,
"name": "Custom run name"
},
{
"suite_id": 1,
"include_all": false,
"case_ids": [
1,
2,
3,
5
]
}
]
}
Testplan.java
#Data
#Builder
public class Testplan {
#JsonProperty("name")
public String name;
#JsonProperty("entries")
public List<Entry> entries = null;
}
Entry.java
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Entry {
#JsonProperty("suite_id")
public Integer suiteId;
#JsonProperty("name")
public String name;
#JsonProperty("include_all")
public Boolean includeAll;
#JsonProperty("case_ids")
public List<Integer> caseIds = null;
}
I convert String to json using this:
public <U> String toJson(U request) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
return mapper.writeValueAsString(request);
}
Here's how I started creating the object and got stuck:
public static Entry getRequestTemplate() {
Entry entry = new Entry();
entry.setName("Here's some name");
entry.setSuiteId(16);
return entry;
}
To see what's happening I added this:
#Test
public void showJson() throws JsonProcessingException {
String json = toJson(getRequestTemplate());
System.out.println(json);
}
I expect to have have to combine these two classes and create a list of case_ids but can't wrap my head around it.
This worked:
Created a new method of Testplan:
public Testplan kek2() {
Testplan testplan = Testplan.builder()
.name("System test")
.entries(Lists.newArrayList(Entry.builder()
.name("Custom run name")
.suiteId(1)
.includeAll(false)
.caseIds(new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5)))
.build()))
.build();
System.out.println(testplan);
return testplan;
}
and then used this method to convert pojo to json:
protected <U> String toJson(U request) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(request);
}

Jackson: deserializing recursive object

I'm trying to parse the filter parameters sent by a KendoUI grid to my web service and am having some issues convincing Jackson to parse this JSON. As far as I know, I can control the format of the parameters that Kendo sends, but I do not know how I would marshal the parameters into a better format so they remain unchangeable for now.
I intend to convert these parameters into a SQL query for an Oracle database.
Example JSON:
{
"filters":
[
{
"field": "Name",
"operator": "contains",
"value": "John"
},
{
"filters": [
{
"field": "Age",
"operator": "gt",
"value": 20
},
{
"field": "Age",
"operator": "lt",
"value": 85
}
],
"logic", "and"
},
{
"field": "Address",
"operator": "doesnotcontain",
"value": "street"
}
],
"logic": "or"
}
Filters. Java
public class Filters {
private List<Filter> filters;
private String logic;
// accessors/mutators/toString
}
Filter.java
public class Filter {
private String field;
private String operator;
private String value;
// accessors/mutators/toString
}
Unit Test
public class KendoGridFilterTest {
private ObjectMapper mapper;
#Before
public void before() {
mapper = new ObjectMapper();
}
#Test
public void jsonParseTest() {
final String json = "{\"filters\":[{\"field\":\"Name\",\"operator\":\"contains\",\"value\":\"John\"},{filters: [{\"field\":\"Age\",\"operator\": \"eq\",\"value\": 85},{\"field\": \"Age\",\"operator\": \"eq\",\"value\": 85}]\"logic\", \"and\",},{\"field\": \"Address\",\"operator\": \"doesnotcontain\",\"value\": \"street\"}],\"logic\":\"or\"}";
Filters filters = mapper.readValue(json, Filters.class);
assertTrue(json.equals(filters.writeValueAsString(filters);
}
}
Errors
com.fasterxml.jackson.databind.UnrecognizedPropertyException: Unrecognized field
'logic'(com.example.Filter) not market as ignorable (3 known properties
"value", "field", "operator")
at [Source: java.io.StringReader#3bb2b8; line: 1, column 76] (through reference
chain: com.example.Filters["filters"]->com.example.Filter["logic"]
I've also tried adding #JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="#id") to the Filters class and get the same errors.
your Filter class is not correct. It should extend Filters.
After correcting your unit test (json is incorrect) it can load your json into a Filters Object.
public class Filter extends Filters {
private String field;
private String operator;
private String value;
// accessors/mutators/toString
}

Categories

Resources