Jackson: deserializing recursive object - java

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
}

Related

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 +
'}';
}
}

Moshi Expected BEGIN_OBJECT but was BEGIN_ARRAY - custom converter ignored

I'm using Moshi as converter for Retrofit, but for one particular request it doesn't work and exception is thrown:
com.squareup.moshi.JsonDataException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at path $.results
The JSON I want to parse:
{
"id": 423,
"results": [
{
"id": "53484dfec3a3684b930000bd",
"iso_639_1": "en",
"iso_3166_1": "US",
"key": "u_jE7-6Uv7E",
"name": "Trailer",
"site": "YouTube",
"size": 360,
"type": "Trailer"
},
{
"id": "57e16bb0c3a36808bc000641",
"iso_639_1": "en",
"iso_3166_1": "US",
"key": "BFwGqLa_oAo",
"name": "Trailer",
"site": "YouTube",
"size": 1080,
"type": "Trailer"
}
]
}
The model classes:
public class VideosResponse {
private int id;
private List<Video> results;
//+ getters & setters
}
public class Video {
private String id;
#Json(name = "iso_639_1")
private String iso6391;
#Json(name = "iso_3166_1")
private String iso31661;
private String key;
private String name;
private String site;
private Integer size;
private String type;
//+getters & setters
}
This is Retrofit call:
#GET("3/movie/{id}/videos")
Call<List<Video>> movieVideos(
#Path("id") int id,
#Query("api_key") String apiKey);
So as you can see I'm expecting list of objects, but the JSON is actually an objecy itself, therefore I prepared my custom converter:
public class VideosJsonConverter {
#FromJson
public List<Video> fromJson(VideosResponse json) {
return json.getResults();
}
}
... and I'm adding it to my Retrofit like that:
public Retrofit provideRetrofit(#Named("baseUrl") String basUrl) {
Moshi moshi = new Moshi.Builder().add(new VideosJsonConverter()).build();
return new Retrofit.Builder()
.baseUrl(basUrl)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build();
}
My custom converter isn't actually called so it looks like Moshi can't convert JSON to my VideosResponse wrapper class. If I change my converter to accept Map<String, Object> it goes there, but not for VideosResponse. It also works when I change my retrofit enpoint to return directly VideosResponse. Is it possible that there is a conflict with other POJO classes (I have similar classes but with a list of different objects)?
The problem is that the adapter is going to be used by both your desired result and the inner list in VideosResponse. So, the adapter is expecting a VideoResponse-formatted JSON blob within the VideoResponse and fails when it finds the real array on reentry.
You can qualify one of the lists to differentiate them.
Here's an example of qualifying the resulting list.
#Retention(RUNTIME)
#JsonQualifier
public #interface Wrapped {
}
public class VideosJsonConverter {
#Wrapped #FromJson
public List<Video> fromJson(VideosResponse json) {
return json.results;
}
#ToJson
public VideosResponse toJson(#Wrapped List<Video> value) {
throw new UnsupportedOperationException();
}
}
#GET("3/movie/{id}/videos")
#Wrapped
Call<List<Video>> movieVideos(
#Path("id") int id,
#Query("api_key") String apiKey);

How construct list of objects from json string coming from api response?

Similar question might be asked before on here, but I had no luck and I was wondering how to extract specific objects like user in from below json string and then construct an ArrayList. However, there is one twist, one of the property directly under Users is a random number, which can be anything!!!
Here is how my json string looks like:
<code>{
"_links": {
},
"count": {
},
"users": {
"123321": { //*Is a random number which can be any number
"_links": {
},
"user": {
"id": "123321",
"name": "...",
"age": "...",
"address": ""
..
}
},
"456654": {
"_links": {
},
"user": {
"id": "456654",
"name": "...",
"age": "...",
"address": ""
...
}
}
...
},
"page": {
}
}
</code>
The java object I would like to transform it to is:
#JsonIgnoreProperties(ignoreUnknown = true) // Ignore any properties not bound here
public class User {
private String id;
private String name;
//setter:getter
}
Note: The transformation should only consider those two fields (id,name), and ignore the rest of the fields from the json response user:{} object.
Ideally, I would like to end up with a list like this:
List<User> users = resulted json transformation should return a list of users!!
Any idea how can I do this please ideally with Jackson JSON Parser/ or maybe GSON?
Since the user keys are random, you obviously can't map them to a named Java field. Instead, you can parse the top-level object as a map and the manually pull out the user objects.
public class UserWrapper {
private User user;
public User getUser() { return user; }
}
public class Root {
private Map<String, UserWrapper> users;
public List<User> getUsers() {
List<User> usersList = new ArrayList();
for (String key : map.keySet()) {
UserWrapper wrapper = map.get(key);
usersList.add(wrapper.getUser());
}
return userList;
}
}
Root root = parseJson();
List<User> users = root.getUsers()
Hope that helps!
jolt transformer is your friend. Use shift with wildcard * to capture arbitrary node value and then standard mappers (Jackson /gson) .

How To Use Jackson's #JsonIdentityInfo for Deserialization of directed Graphs?

I want to use Jackson 2.3.3 for Deserialization/Serialization of directed graphs. The structure I came up with is roughly the following:
public Class Graph {
private final Set<Node> nodes;
public Graph(Set<Node> nodes) { ... }
public Set<Node> getNodes() { ... }
}
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "name")
public Class Node {
private final String name;
private final Set<Edge> edges;
public Node(String name, Set<Edge> edges) { ... }
public String getName() { ... }
public Set<Edge> getEdges() { ... }
}
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "name")
public Class Edge {
private final String name;
private final Node successor;
public Edge(String name, Node successor) { ... }
public String getName() { ... }
public Node getSuccessor() { ... }
}
And I expect to have this JSON-Structure:
{
"graph": [{
"name": "A",
"edges": [{
"name": "0",
"successor": "B"
}, {
"name": "1",
"successor": "A"
}]
}, {
"name": "B",
"edges": [{
"name": "0",
"successor": "A"
}, {
"name": "1",
"successor": "B"
}]
}]
}
But I get the following error while deserialization (even with annotation #JsonProperty("name") at the Getters):
com.fasterxml.jackson.databind.JsonMappingException: Invalid Object Id definition for some.package.graph.Node: can not find property with name 'name'
I have found some solutions for Jackson 1.6 with Back-Reference Annotations, but I'd like to use the new Jackson 2.x Annotation, as it was advertised so much in the API Update from 1.9 to 2.0 of Jackson.
What point am I missing here? Thanks for constructive answers in advance.
EDIT
(Removed my answer from here to the Answer section)
I got kind of blind of staring too long at it. Here's what's gone wrong:
The Serialization actually worked as intended. What didn't work was the Deserialization, because Jackson wasn't able to instantiate my Node-Object. I simply forgot to annotate the parameters of the constructor methods correctly.
I was now facing another problem. The generated JSON now looked like this:
"graph": {
"nodes": [{
"name": "B",
"edges": [{
"label": "1",
"successor": "B"
}, {
"label": "0",
"successor": {
"name": "A",
"edges": [{
"label": "1",
"successor": "A"
}, {
"label": "0",
"successor": "B"
}]
}
}]
}, "A"]
}
So far so good. But during mapping, Jackson confronts me with this Error:
java.lang.IllegalStateException: Could not resolve Object Id [B] (for [simple
type, class some.package.graph.Node]) -- unresolved forward-reference?
I even changed the Label of the edges because I thought the same property name might confuse Jackson here, but that didn't help either...
My guess here is that Jackson can't reference the Node B, because it is still being constructed (you could say it is actually some kind of root in this example). The only way to fix this seems to construct all the Nodes without the edges and inject them in a second step.

Categories

Resources