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.
Related
I am very new to spring boot development.
Currently, I am using #FeignClient to call external API and I want to bind it to my DTO.
My DTO looks like
public class TestDTO {
private UUID uuid;
private String name;
}
My #FeignClient,
#FeignClient(name = "testClient", url = "http://extenalApi/getRules")
public interface DataClient {
#RequestMapping(method = RequestMethod.GET)
List<TestDTO> getRules();
}
It throws an error because external API response is a bit different
{
"data": [
{
"customRule": {
"name": "SAMPLE 1",
"id": "0AB58A47D3A64B56A6B74DA0E66935DD"
}
},
{
"customRule": {
"name": "SAMPLE 2",
"id": "0AE6C846EAF648D1926E665E63633A94"
}
}
}
how can I parse this JSON and make it to
[
{
"name": "SAMPLE 2",
"id": "0AE6C846EAF648D1926E665E63633A94"
},
{ ...
}
]
as my DTO demands.
Seems like you have different structure of API reponse.
Create a new container to hold API response of rules api:
public class RulesDTO {
private List<DataDto> data;
//getter setter
class DataDto{
TestDTO customRule;
//getter setter
}
}
change method to getRules:
#RequestMapping(method = RequestMethod.GET)
RulesDTO getRules();
Now parse it to List of TestDTO for your response:
List<TestDTO> yourData = data.stream().map(DataDto::getCustomRule).collect(Collectors.toList());
Note- It is not working code. Just to give you an idea.
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 +
'}';
}
}
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);
I use Spring MVC and Jackson to drive the API of a application that I work in. I am faced with the following situation, we need serialize the Person class below in two different ways...
#Entity
Order{
String id;
String name;
String address;
List<Items> items;
}
#Entity
Item{
String id;
String description:
}
The two situations reposes on the serialization or not of the content of the "items" field in accord with the service that was called.
For example, the service http://localhost/order, results without the "items" field.
{
"id": "1",
"name" : "Bill",
"address" : "any address",
}
In the other hands, the second way is http://localhost/order/[id_order]/item/[ids_items], results with the field "items" that was give on the parameter.
{
"id": "1",
"name" : "Bil",
"address" : "any",
"items" : [{
"id" : "33",
"description" : "Item 33"
}]
}
#JsonView
You can use #JsonView to filter fields depending on the context of serialization. It is supported by Spring MVC.
First define your views:
public class View {
interface Default { }
interface Detailed extends Default { }
}
Then annotate your fields using the desired view:
#Entity
public class Order {
#JsonView(View.Default.class)
private String id;
#JsonView(View.Default.class)
private String name;
#JsonView(View.Default.class)
private String address;
#JsonView(View.Detailed.class)
private List<Items> items;
// Getters and setters
}
Finally annotate your controller methods to use a view when serializing the response:
#JsonView(View.Default.class)
#RequestMapping(value = "/order", method = RequestMethod.GET)
public ResponseEntity<Order> getOrder() {
...
}
#JsonView(View.Detailed.class)
#RequestMapping(value = "/order-with-items", method = RequestMethod.GET)
public ResponseEntity<SampleResults> getOrderWithItems() {
...
}
In order to make it work, you may need to disable the default view inclusion in your ObjectMapper:
mapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION);
With jackson you can modify the result json string on the fly. For example:
// create a new order
Order order = new Order("id", "name", "addr");
ObjectMapper mapper = new ObjectMapper();
// create a json string with the order
JsonNode node = mapper.valueToTree(order);
//the content of the node at this moment is:
//{"id":"id","name":"name","address":"addr"}
// create an ArrayList with the Items
ArrayList<Item> items = new ArrayList<Item>();
items.add(new Item("id1", "desc1"));
items.add(new Item("id2", "desc2"));
// transform the ArrayList to a json string and add it
// the the previous node with the Order
((ObjectNode)node).put("items", mapper.valueToTree(items));
String jsonString = node.toString();
System.out.println(jsonString);
The final output is:
{"id":"id","name":"name","address":"addr","items":[{"id":"id1","description":"desc1"},{"id":"id2","description":"desc2"}]}
For more information visit the official documentation page: https://github.com/FasterXML/jackson-databind/
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
}