Reputation: 125
I am developing a library to be able to query a public api in a more fluent way for a personal project. And I have some difficulty with Jackson to transform the API answers into my java objects.
There is my problem :
I call this API using jdk 11 HttpClient and using a BodyHandlers.ofString(). So far, nothing very complicated. But the format of the answer gives me difficulties when I want to transform the json into a java object.
Here is an answer in Json :
{
"data": {
"iso": "2021-02-04T20:35:21Z",
"epoch": 1612470921
}
}
But the "data" type can also be in other answers with different fields. For example :
{
"data": {
"base": "BTC",
"currency": "USD",
"amount": "37721.43"
}
}
So I decided to create a generic data type like this :
public class DataDto<T> {
T data;
@JsonCreator
public DataDto(@JsonProperty(value = "data") T data) {
this.data = data;
}
}
So, Let's take the example of the first answer. I have an other object called Time :
public class TimeDto {
private LocalDateTime iso;
private long epoch;
@JsonCreator
public TimeDto(
@JsonProperty(value = "iso") LocalDateTime iso, @JsonProperty(value = "epoch") long epoch){
this.iso = iso;
this.epoch = epoch;
}
}
And there is my function where I want to transform the answer into my java object :
public static Time getTime(final MyClient client) {
var request =
HttpRequest.newBuilder()
.GET()
.uri(
URI.create(
client.getProperties().getApiUrl() + client.getProperties().getTimePath()))
.header(ACCEPT, ACCEPT_VALUE)
.build();
try {
var response = client.getClient().send(request, HttpResponse.BodyHandlers.ofString());
var dataDtoType = client.getObjectMapper().getTypeFactory().constructType(DataDto.class);
var timeDtoType =
client
.getObjectMapper()
.getTypeFactory()
.constructParametricType(TimeDto.class, dataDtoType);
var toto = client.getJsonDeserializer().readValue(response.body(), timeDtoType);
return null;
} catch (Exception e) {
log.error("An error occured while getting time", e);
throw new MyException("An error occured while getting time", e);
}
}
As you can see, I tried to create a specific Jackson JavaType to be able to transform data in a generic way... But in my case, when I test this code, this exception appends :
java.lang.IllegalArgumentException: Cannot create TypeBindings for class com.github.badpop.mylib.client.dto.TimeDto with 1 type parameter: class expects 0
at com.fasterxml.jackson.databind.type.TypeBindings.create(TypeBindings.java:126)
at com.fasterxml.jackson.databind.type.TypeBindings.create(TypeBindings.java:96)
at com.fasterxml.jackson.databind.type.TypeFactory.constructParametricType(TypeFactory.java:1109)
at com.github.badpop.mylib.client.PublicResourcesService.getTime(PublicResourcesService.java:42)
at com.github.badpop.mylib.client.MyClient.getTime(MyClient.java:70)
at com.github.badpop.mylib.Main.main(Main.java:20)
If you have an idea that could help me solve my problem, thank you in advance!
Upvotes: 0
Views: 1632
Reputation: 159086
The easiest is to use a TypeReference
, e.g.
String json = "{\r\n" +
" \"data\": {\r\n" +
" \"iso\": \"2021-02-04T20:35:21Z\",\r\n" +
" \"epoch\": 1612470921\r\n" +
" }\r\n" +
"}";
ObjectMapper mapper = new ObjectMapper()
.registerModule(new JavaTimeModule());
DataDto<TimeDto> data = mapper.readValue(json, new TypeReference<DataDto<TimeDto>>() {});
System.out.println("iso: " + data.getData().getIso());
System.out.println("epoch: " + data.getData().getEpoch());
You can even "skip" the DataDto
part:
TimeDto time = mapper.readValue(json, new TypeReference<DataDto<TimeDto>>() {}).getData();
System.out.println("iso: " + time.getIso());
System.out.println("epoch: " + time.getEpoch());
Output
iso: 2021-02-04T20:35:21Z
epoch: 1612470921
DTOs
class DataDto<T> {
private final T data;
@JsonCreator
public DataDto(@JsonProperty(value = "data") T data) {
this.data = data;
}
public T getData() {
return this.data;
}
}
class TimeDto {
private final Instant iso;
private final long epoch;
@JsonCreator
public TimeDto(@JsonProperty(value = "iso") Instant iso,
@JsonProperty(value = "epoch") long epoch){
this.iso = iso;
this.epoch = epoch;
}
public Instant getIso() {
return this.iso;
}
public long getEpoch() {
return this.epoch;
}
}
Upvotes: 1