I'm trying to get responce from another API convert it to Java clases and then sent to the front end using Spring Boot
I have JSON response from external API like this
{
"status": {
"timestamp": "2023-01-31T14:06:45.210Z",
"error_code": 0,
"error_message": null,
},
"data": [
{
"id": 7982,
"name": "wc4qtz6py1i",
"tags": [
"40rcevshzab",
"3ja25pufu0z"
],
"quote": {
"USD": {
"price": 0.2,
},
"BTC": {
"price": 7159,
}
}
},
{
"id": 8742,
"name": "uhso98ca",
"tags": [
"84jsjsaesxx",
"sasdd5dda76"
],
"quote": {
"USD": {
"price": 6,
},
"BTC": {
"price": 1230,
}
}
}
]
}
I need to convert all of this to classes using Spring Boot. But how I should organize it?
The most question is about "Data" array.
For now I have something like this.
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
public class MainDTO{
#JsonProperty("status")
private Status status;
#JsonProperty("data")
private Data data;
}
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
public class Data {
Coin[] coins;
}
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
public class Coin{
#JsonProperty("id")
private Integer id;
#JsonProperty("name")
private String name;
private Map<String, Coin> quote;
}
But it doesn't work. I have error:
I have error
Tue Jan 31 16:13:45 EET 2023
There was an unexpected error (type=Internal Server Error, status=500).
Error while extracting response for type [class com.example.myCoolApp.entity.MainDTO] and content type [application/json;charset=utf-8]
org.springframework.web.client.RestClientException: Error while extracting response for type [class com.example.myCoolApp.entity.CryptoDTO] and content type [application/json;charset=utf-8]
You do not need a Data class because data field in your json is not an object - it is an array of objects, in your case it is an array of Coin objects.
Change it the next way:
public class MainDTO{
#JsonProperty("status")
private Status status;
#JsonProperty("data")
private List<Coin> data;
}
It's worth mentioning that the JSON in your example is incorrect due to extra commas. I presume the correct form should look like this:
{
"status": {
"timestamp": "2023-01-31T14:06:45.210Z",
"error_code": 0,
"error_message": null
},
"data": [
{
"id": 7982,
"name": "wc4qtz6py1i",
"tags": [
"40rcevshzab",
"3ja25pufu0z"
],
"quote": {
"USD": {
"price": 0.2
},
"BTC": {
"price": 7159
}
}
},
{
"id": 8742,
"name": "uhso98ca",
"tags": [
"84jsjsaesxx",
"sasdd5dda76"
],
"quote": {
"USD": {
"price": 6
},
"BTC": {
"price": 1230
}
}
}
]
}
As for classes I would suggest you the next:
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
public class MainDTO {
#JsonProperty("status")
private Status status;
#JsonProperty("data")
private List<Coin> coins;
}
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
public class Status {
#JsonProperty("timestamp")
private String timestamp;
#JsonProperty("error_code")
private Integer errorCode;
#JsonProperty("error_message")
private String errorMessage;
}
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
public class Coin {
#JsonProperty("id")
private Integer id;
#JsonProperty("name")
private String name;
#JsonProperty("tags")
private List<String> tags;
#JsonProperty("quote")
private Map<String, Quote> quote;
}
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
public class Quote {
#JsonProperty("price")
private Double price;
}
The "data" array in the JSON response is an array of coins, so the coins property in the MainDTO class should be of type List<Coin>.
Related
I have been wondering what exactly I am doing wrong here. The response I am getting from my POJO class has a root property that I am unable to remove.
I have this JSON response:
{
"page": 1,
"per_page": 6,
"total": 12,
"total_pages": 2,
"data": [
{
"id": 1,
"name": "cerulean",
"year": 2000,
"color": "#98B2D1",
"pantone_value": "15-4020"
},
{
"id": 2,
"name": "fuchsia rose",
"year": 2001,
"color": "#C74375",
"pantone_value": "17-2031"
}
],
"support": {
"url": "https://reqres.in/#support-heading",
"text": "To keep ReqRes free, contributions towards server costs are appreciated!"
}
}
I converted JSON to these POJO classes and ignore properties not required for my test.
First POJO
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Datum{
public int id;
public String name;
public int year;
public String color;
public String pantone_value;
}
Second POJO
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Root {
#JsonIgnore
public int page;
#JsonIgnore
public int per_page;
#JsonIgnore
public int total;
#JsonIgnore
public int total_pages;
public ArrayList<Datum> data;
#JsonIgnore
public Support support;
}
Third POJO:
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Support {
public String url;
public String text;
}
I want to get the properties in the Responses' Data list and convert it to a map, so I did this:
public void verify( List<Map<String, String>> myTest) { //myTest holds the expected response i want to use for my assertion
Root response = (resp.as(Root.class));
Map<String, Object> mapResponse = mapper.convertValue(response, new TypeReference<>() {
});
System.out.println(mapResponse);
}
Output:
{data=[{id=1, name=cerulean, year=2000, color=#98B2D1, pantone_value=15-4020}, {id=2, name=fuchsia rose, year=2001, color=#C74375, pantone_value=17-2031}, {id=3, name=true red, year=2002, color=#BF1932, pantone_value=19-1664}]}
The {data= root property (key) at beginning of the output is what I was trying to remove as it's making my assertion fail.
This is the output I would like:
[{id=1, name=cerulean, year=2000, color=#98B2D1, pantone_value=15-4020}, {id=2, name=fuchsia rose, year=2001, color=#C74375, pantone_value=17-2031}]
How can I convert the response's data format to get this?
You can convert only data list
List<Map<String, Object>> mapResponse = mapper.convertValue(response.getData(), new TypeReference<>() {
});
System.out.println(mapResponse);
Json:
{
"response": {
"count": 3,
"items": [
6651536,
20410167,
40345521
]
}
}
NOT WORK - FriendsDTO:
#Jacksonized
#Builder
#Value
public class FriendsDTO {
int count;
#JsonProperty("items")
List<Integer> friends;
}
WORK - FriendsDTO:
#Jacksonized
#Builder
#Value
public class FriendsDTO {
Response response;
#Jacksonized
#Builder
#Value
public static class Response {
int count;
#JsonProperty("items")
List<Integer> friends;
}
}
WebFluxClient request:
FriendsDTO friends = webFluxClient.get()
.uri(config.methodGetFriends(), builder -> {
builder.queryParam("access_token", accessToken);
builder.queryParam("v", config.version());
return builder.build();
})
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(FriendsDTO.class)
.log()
.block();
logger.info(friends.toString());
output WORK: FriendsDTO(response=FriendsDTO.Response(count=3, friends=[6651536, 20410167, 40345521]))
output NOT WORK: FriendsDTO(count=0, friends=null)
How can I get rid of this misunderstanding in the Response view for successful json parsing ?
Thats the expected behaviour
{
name : "name",
description : "description"
}
This can be written as
class <ClassName>{
private String name;
private String description;
}
The json does not carry the ClassName. Similarly in your case, the word Response should be a valid attribute, like how I defined name in this example.
So , if you want , this class to work
#Jacksonized
#Builder
#Value
public class FriendsDTO {
int count;
#JsonProperty("items")
List<Integer> friends;
}
You JSON should be changed from
{
"response": {
"count": 3,
"items": [
6651536,
20410167,
40345521
]
}
}
To
{
"count": 3,
"items": [
6651536,
20410167,
40345521
]
}
I have this JSON: (passed as Map<String, Object>)
{
"id": 1000,
"lab": [
"LAB1",
"LAB2",
"LAB3"
],
"name": "TEST",
"ref": {
"id": 1000,
"code": "REFCODE",
"description": "REF DESC"
},
"employee": {
"id": 1000,
"name": "Emp1000",
"tin": null,
"active": true
},
"contacts": [
{
"id": 1000,
"name": "Contact 1",
"emailAddress": "contact1#test.com",
"active": true,
"positions": [
{
"position": {
"id": 1000,
"code": "POS",
"description": "POS DESC"
}
}
]
}
],
"status": "NEW"
}
This is my DTO and ContactDTO:
public class DTO {
private Long id;
...
#JsonProperty("contacts")
private List<ContactDTO> contacts;
}
#Builder
public class ContactDTO implements Serializable {
private Long id;
private String name;
private String emailAddress;
private Boolean active;
#NotEmpty
private List<ContactPositionDTO> positions;
}
Here is my service class with object mapper and process method which accepts the JSON map:
private ObjectMapper objectMapper() {
var objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
return objectMapper;
}
public void process(final Map<String, Object> map) {
objectMapper().convertValue(map, DTO.class);
}
However, I am getting java.lang.IllegalArgumentException: Cannot deserialize value of type java.util.ArrayList
And if I add DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY I am getting a different error:
java.lang.IllegalArgumentException: Cannot construct instance of ContactDTO (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('[{id=1000, name=Contact 1, .....
You have two alternative options to fix your ContactDTO class:
Add a no-arguments constructor
public ContactDTO() { }
to the class. To fix the then upcoming compiler error
you will need to remove the #Builder annotation.
Keep the #Builder annotation
and add the #Jacksonized annotation to the class.
This will configure the generated builder to cooperate
with Jackson's deserialization.
For more details see Lombok's documentation about #Jacksonized.
To deserialize you need a No arg constructor and to use #Builder you need an all arg constructor.
So you need tu add both.
The example below should work. I just added #Getter annotation to avoid using #JsonProperty
#Getter
public static class DTO {
private Long id;
#JsonProperty("contacts")
private List<ContactDTO> contacts;
}
#Builder
#Getter
#NoArgsConstructor
#AllArgsConstructor
public static class ContactDTO implements Serializable {
private Long id;
private String name;
private String emailAddress;
private Boolean active;
private List<ContactPositionDTO> positions;
}
#Getter
public static class ContactPositionDTO {
private Position position;
#JsonProperty("effectiveStartDate")
private List<Integer> date;
#Getter
static class Position {
private Integer id;
private String code;
private String description;
}
}
NB: you can also use #Jacksonized annotation instead of #NoArgsConstructor and #AllArgsConstructor
How can I use MapStruct to create a mapper that maps from Model entity that includes one list of objects and one another object to Domain entity, consists of only list of nested objects.
My Model entity list object = SourceObject-A;
My Model entity second object = SourceObject-B;
My Doamin entity list object = TargetObject-AB;
My source classes looks like this:
SourceObject-A:
#NoArgsConstructor
#AllArgsConstructor
#Data
public class User {
private int id;
private String name;
}
SourceObject-B:
#NoArgsConstructor
#AllArgsConstructor
#Data
public class CountryDetails {
private String country;
private String countryCode;
}
So I need to tranform it to this(TargetObject-AB):
#NoArgsConstructor
#AllArgsConstructor
#Data
public class DomainUser {
private int id;
private String name;
private String country;
private String countryCode;
}
UserController:
GetMapping("/users")
public List<DomainUser> getDUsers(List<User> usersList, CountryDetails countryDetails){
List<DomainUser> domainUsersList=ModelToDomainMapper.INSTANCE.UserToDUser(usersList,
countryDetails);
return domainUsersList;
}
Mapper Interface:
#Mapper
public interface ModelToDomainMapper {
ModelToDomainMapper INSTANCE = Mappers.getMapper(ModelToDomainMapper.class)
List<DomainUser> UserToDUser(List<User> users, CountryDetails countryDetails);
}
Expected sample json:
Source(Input):
[
"countryDetails":{
"country" : "India",
"countryCode" : "+91"
},
"userslist" :[
{
"id" : 1,
"name" : "XXXXXXX"
},
{
"id" : 2,
"name" : "XXXXXXX"
}
]
]
Target(Expected Output):
[
{
"id": 1,
"name": "xxxxxx",
"country": "India",
"countryCode": "+91"
},
{
"id": 2,
"name": "xxxxxx",
"country": "India",
"countryCode": "+91"
}
]
Is there any way to get this above output please help me.
You could do something like this in your mapper:
DomainUser UserToDUser(User user, CountryDetails countryDetails);
default List<DomainUser> UsersToDomainUsers(List<User> users, CountryDetails countryDetails) {
return users.stream()
.map((user -> UserToDUser(user, countryDetails)))
.collect(Collectors.toList());
}
This would use the MapStruct generated mapper to merge every single User with the same CountryDetails to create a DomainUser and collect them all into a List using streams.
Trying to serialize a collection of non-primitive types using katharsis, but getting an empty collection all the time.
Response example:
{
"data": {
"type": "products",
"id": "1",
"attributes": {
"simpleAttributes": [
{}
],
"variationGroup": "variationGroup"
},
"relationships": {},
"links": {
"self": "http://localhost:8080/api/products/1"
}
},
"included": []
}
Expected response:
{
"data": {
"type": "products",
"id": "1",
"attributes": {
"simpleAttributes": [
{
tittle: "some title",
value: "some value"
}
],
"variationGroup": "variationGroup"
},
"relationships": {},
"links": {
"self": "http://localhost:8080/api/products/1"
}
},
"included": []
}
Domain objects (getters, setters, constructor and other stuff omitted by using lombok #Data annotation):
#JsonApiResource(type = "products")
#Data
public class Product {
#JsonApiId
private Integer id;
private List<SimpleAttribute> simpleAttributes = new ArrayList<>();
private String variationGroup;
}
#Data
public class SimpleAttribute implements Serializable{
private String title;
private String value;
}
I do not want to use relationships in this case or to include attributes to "included" field. Is it possible in katharsis?
Not sure what actually was wrong, but the problem disappeared after I changed katharsis-spring version from 2.3.0 to 2.3.1.