Bad_Pop
Bad_Pop

Reputation: 125

Jackson, how to transform json to Java class using raw type?

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

Answers (1)

Andreas
Andreas

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

Related Questions