3

Lets suppose, that we have a bean like this:

public class Response<T> {

    private T data;
    private double executionDuration;
    private boolean success;
    private String version;

    //HOW TO Make Jackson to inject this?
    private Class<T> dataClass;

    public Optional<T> getData() {
        return Optional.ofNullable(data);
    }

    public double getExecutionDuration() {
        return executionDuration;
    }

    public Class<T> getDataClass() {
        return dataClass;
    }

    public String getVersion() {
        return version;
    }

    public boolean isSuccess() {
        return success;
    }
}

The deserialization happens like this:

objectMapper.readValue(json, new TypeReference<Response<SomeClass>>() {});

Can I somehow make Jackson to inject the class "SomeClass" into my bean? Injecting the type reference itself would be also ok, I think.

buræquete
  • 14,226
  • 4
  • 44
  • 89
Gábor Lipták
  • 9,646
  • 2
  • 59
  • 113
  • can you check this once -> https://stackoverflow.com/questions/11664894/jackson-deserialize-using-generic-class – Vinay Veluri Jun 11 '19 at 14:14
  • 1
    @VinayVeluri in json I have no reference on the "dataClass". The only way, how Jackson knows that is the type reference. This is why I wondered if jackson can inject this information. – Gábor Lipták Jun 11 '19 at 14:20
  • It cannot do that, you might want to do that yourself by reading a value with class as string, and then loading the class with Class.forName. Not only Jackson but you yourself cannot assume you know that class (the class can be classloaded). And then parsing it again with a recognized class – maslan Jun 11 '19 at 14:31
  • @maslan If I give in the typereference for deserializing, jackson could really simply do the injection. – Gábor Lipták Jun 12 '19 at 06:23
  • 1
    why not to call ```data.getClass()```? – Nolequen Jun 14 '19 at 06:54
  • @Nolequen if the response is empty, then the Class would be Void and the data is null. If I call .getClass on a null, then I get NullPointerException. – Gábor Lipták Jun 14 '19 at 06:57
  • 1
    you may add check for null in ```getDataClass``` method: ```data == null ? data.getClass() : Void.class``` – Nolequen Jun 16 '19 at 14:58
  • @buræquete no. None of the answers is answering my question. – Gábor Lipták Jul 19 '19 at 11:02
  • @GáborLipták not sure why my answer isn't at all helpful, can you please comment on it? – buræquete Jul 19 '19 at 14:26
  • @buræquete if the value is not present, your solution cannot tell, which type was given into the objectmapper. – Gábor Lipták Jul 22 '19 at 19:44

3 Answers3

3

If it is undesirable to save class info in json and use @JsonTypeInfo I would suggest to use @JacksonInject:

  public class Response<T> {
    private T data;
    private double executionDuration;
    private boolean success;
    private String version;

    @JacksonInject("dataClass")
    private Class<T> dataClass;

    public Optional<T> getData() {
      return Optional.ofNullable(data);
    }

    public double getExecutionDuration() {
      return executionDuration;
    }

    public Class<T> getDataClass() {
      return dataClass;
    }

    public String getVersion() {
      return version;
    }

    public boolean isSuccess() {
      return success;
    }
  }

Deserialization would look like:

ObjectMapper mapper = new ObjectMapper();
InjectableValues.Std injectable = new InjectableValues.Std();
injectable.addValue("dataClass", SomeClass.class);
mapper.setInjectableValues(injectable);
final Response<Integer> response = mapper.readValue(json, new TypeReference<Response<SomeClass>>() { });
Nolequen
  • 3,032
  • 6
  • 36
  • 55
1

this worked for me;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
public class Entity<T> {
    private T data;
    @JsonSerialize(converter = ClassToStringConverter.class)
    @JsonDeserialize(converter = StringToClassConverter.class)
    private Class<T> dataClass;
}

and

import com.fasterxml.jackson.databind.util.StdConverter;

public class ClassToStringConverter extends StdConverter<Class<?>, String> {

    public String convert(Class<?> aClass) {
//        class java.lang.Integer
        return aClass.toString().split("\\s")[1];
    }
}

and

import com.fasterxml.jackson.databind.util.StdConverter;

public class StringToClassConverter extends StdConverter<String, Class<?>> {

    public Class<?> convert(String s) {
        try {
            return Class.forName(s);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}

Main;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;

public class Main {

    public static void main(String[] args) throws IOException {
        Entity<Integer> data = new Entity<Integer>();
        data.setData(5);
        data.setDataClass(Integer.class);

        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(data);

        Entity<Integer> jsonData = mapper.readValue(json, new TypeReference<Entity<Integer>>() {});
        System.out.println(jsonData.getData());
        System.out.println(jsonData.getDataClass().getCanonicalName());
    }
}

But, maybe it will be better, to not save the class type, but use method to get type from data?

public Class<T> getType() {
    return (Class<T>) data.getClass();
}
buræquete
  • 14,226
  • 4
  • 44
  • 89
krund
  • 740
  • 1
  • 7
  • 16
0
public class Response<T> {
    private T data;

    // other fields & methods

    public Class getType() {
        return Optional.ofNullable(data).map(Object::getClass).orElse(Void.class);
    }

    public Optional<Class> getSafeType() {
        return Optional.ofNullable(data).map(Object::getClass);
    }
}

Super simple, no need to tinker with Jackson, NPE safe...

buræquete
  • 14,226
  • 4
  • 44
  • 89