Is there a way I can make the static method toObject generic by passing the T class and return type T?
public class JsonUtil {
private JsonUtil() {
}
public static Object toObject(String jsonString, Class clazz, boolean unwrapRootValue) throws TechnicalException {
ObjectMapper mapper = new ObjectMapper();
mapper.writerWithDefaultPrettyPrinter();
if (unwrapRootValue) mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
try {
return mapper.readValue(jsonString, clazz);
} catch (IOException e) {
throw new TechnicalException("Exception while converting JSON to Object", e);
}
}
}
Sure. Just specify a generic type parameter on the method itself, and use it for both the return type and the clazz parameter:
public static <T> T toObject(String jsonString, Class<T> clazz,
boolean unwrapRootValue) throws TechnicalException {
/* ... */
}
public class JsonUtil {
private JsonUtil() {
}
public static <T> T toObject(String jsonString, Class<? extends T> clazz, boolean unwrapRootValue) throws TechnicalException {
ObjectMapper mapper = new ObjectMapper();
mapper.writerWithDefaultPrettyPrinter();
if (unwrapRootValue) mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
try {
return mapper.readValue(jsonString, clazz);
} catch (IOException e) {
throw new TechnicalException("Exception while converting JSON to Object", e);
}
}
}
Related
I want to use jackson json library for a generic method as follows:
public MyRequest<T> tester() {
TypeReference<MyWrapper<T>> typeRef = new TypeReference<MyWrapper<T>>();
MyWrapper<T> requestWrapper = (MyWrapper<T>) JsonConverter.fromJson(jsonRequest, typeRef);
return requestWrapper.getRequest();
}
public class MyWrapper<T> {
private MyRequest<T> request;
public MyRequest<T> getRequest() {
return request;
}
public void setRequest(MyRequest<T> request) {
this.request = request;
}
}
public class MyRequest<T> {
private List<T> myobjects;
public void setMyObjects(List<T> ets) {
this.myobjects = ets;
}
#NotNull
#JsonIgnore
public T getMyObject() {
return myobjects.get(0);
}
}
Now the problem is that when I call getMyObject() which is inside the request object Jackson returns the nested custom object as a LinkedHashMap. Is there any way in which I specify that T object needs to be returned? For example: if I sent object of type Customer then Customer should be returned from that List?
This is a well-known problem with Java type erasure: T is just a type variable, and you must indicate actual class, usually as Class argument. Without such information, best that can be done is to use bounds; and plain T is roughly same as 'T extends Object'. And Jackson will then bind JSON Objects as Maps.
In this case, tester method needs to have access to Class, and you can construct
JavaType type = mapper.getTypeFactory().
constructCollectionType(List.class, Foo.class)
and then
List<Foo> list = mapper.readValue(new File("input.json"), type);
'JavaType' works !!
I was trying to unmarshall (deserialize) a List in json String to ArrayList java Objects and was struggling to find a solution since days.
Below is the code that finally gave me solution.
Code:
JsonMarshallerUnmarshaller<T> {
T targetClass;
public ArrayList<T> unmarshal(String jsonString) {
ObjectMapper mapper = new ObjectMapper();
AnnotationIntrospector introspector = new JacksonAnnotationIntrospector();
mapper.getDeserializationConfig()
.withAnnotationIntrospector(introspector);
mapper.getSerializationConfig()
.withAnnotationIntrospector(introspector);
JavaType type = mapper.getTypeFactory().
constructCollectionType(
ArrayList.class,
targetclass.getClass());
try {
Class c1 = this.targetclass.getClass();
Class c2 = this.targetclass1.getClass();
ArrayList<T> temp = (ArrayList<T>)
mapper.readValue(jsonString, type);
return temp ;
} catch (JsonParseException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null ;
}
}
I modified rushidesai1's answer to include a working example.
JsonMarshaller.java
import java.io.*;
import java.util.*;
public class JsonMarshaller<T> {
private static ClassLoader loader = JsonMarshaller.class.getClassLoader();
public static void main(String[] args) {
try {
JsonMarshallerUnmarshaller<Station> marshaller = new JsonMarshallerUnmarshaller<>(Station.class);
String jsonString = read(loader.getResourceAsStream("data.json"));
List<Station> stations = marshaller.unmarshal(jsonString);
stations.forEach(System.out::println);
System.out.println(marshaller.marshal(stations));
} catch (IOException e) {
e.printStackTrace();
}
}
#SuppressWarnings("resource")
public static String read(InputStream ios) {
return new Scanner(ios).useDelimiter("\\A").next(); // Read the entire file
}
}
Output
Station [id=123, title=my title, name=my name]
Station [id=456, title=my title 2, name=my name 2]
[{"id":123,"title":"my title","name":"my name"},{"id":456,"title":"my title 2","name":"my name 2"}]
JsonMarshallerUnmarshaller.java
import java.io.*;
import java.util.List;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
public class JsonMarshallerUnmarshaller<T> {
private ObjectMapper mapper;
private Class<T> targetClass;
public JsonMarshallerUnmarshaller(Class<T> targetClass) {
AnnotationIntrospector introspector = new JacksonAnnotationIntrospector();
mapper = new ObjectMapper();
mapper.getDeserializationConfig().with(introspector);
mapper.getSerializationConfig().with(introspector);
this.targetClass = targetClass;
}
public List<T> unmarshal(String jsonString) throws JsonParseException, JsonMappingException, IOException {
return parseList(jsonString, mapper, targetClass);
}
public String marshal(List<T> list) throws JsonProcessingException {
return mapper.writeValueAsString(list);
}
public static <E> List<E> parseList(String str, ObjectMapper mapper, Class<E> clazz)
throws JsonParseException, JsonMappingException, IOException {
return mapper.readValue(str, listType(mapper, clazz));
}
public static <E> List<E> parseList(InputStream is, ObjectMapper mapper, Class<E> clazz)
throws JsonParseException, JsonMappingException, IOException {
return mapper.readValue(is, listType(mapper, clazz));
}
public static <E> JavaType listType(ObjectMapper mapper, Class<E> clazz) {
return mapper.getTypeFactory().constructCollectionType(List.class, clazz);
}
}
Station.java
public class Station {
private long id;
private String title;
private String name;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return String.format("Station [id=%s, title=%s, name=%s]", id, title, name);
}
}
data.json
[{
"id": 123,
"title": "my title",
"name": "my name"
}, {
"id": 456,
"title": "my title 2",
"name": "my name 2"
}]
I am trying to use TypeAdapterFactory to serialize and deserialize some customer objects. I would like to serialize all the objects to a particular type at runtime.
So given a String classpath and a JsonObject object I would like to deserialize the object to an instance of Class.forName(classpath).
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> tokenType)
{
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, tokenType);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<T>()
{
#Override
public T read(JsonReader reader) throws IOException
{
Class<?> clazz = Class.forName(classpath);
JsonObject jsonObject = elementAdapter.read(reader).getAsJsonObject();
// Here I want to return an instance of clazz
}
#Override
public void write(JsonWriter writer, T value) throws IOException
{
}
};
}
How would I go about this?
You can try something like this (code wont compile, you need to catch exceptions). Maybe there is a better syntax for THIS too.
final class MyClass implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> tokenType) {
final MyClass THIS = this;
return new TypeAdapter<T>() {
#Override
public T read(JsonReader reader) throws IOException {
Class<?> clazz = Class.forName(classpath);
TypeToken<T> token = (TypeToken<T>) TypeToken.get(clazz);
TypeAdapter<T> adapter = gson.getDelegateAdapter(THIS, token);
JsonElement tree = gson.getAdapter(JsonElement.class).read(reader);
T out = adapter.fromJsonTree(tree);
return out;
}
#Override
public void write(JsonWriter writer, T value) throws IOException {
}
};
}
}
I have a lot of identical classes. I want to improve it using generics:
public class PingInitializer extends AbstractHandler implements DataWarehouseInitializer<PingInteraction, PingInvocation> {
#Handler
#Override
public PingInteraction handle(Message message) throws IOException, MessageException {
checkMessageIsNotNull(message);
PingInvocation invocation = construct(message.getBody().toString());
l.debug("handling in initializer... {}", invocation);
return new PingInteraction(invocation);
}
public PingInvocation construct(String message) throws IOException, MessageException {
ObjectMapper mapper = new ObjectMapper();
PingInvocation invocation;
try {
invocation = mapper.readValue(message, PingInvocation.class);
} catch (IOException e) {
throw new MessageException("Can't deserialize message", e);
}
return invocation;
}
}
I want to create new abstract class AbstractInitializer and all child class just have to specify generic type:
public abstract class AbstractInitializer<INTERACTION, INVOCATION> extends AbstractHandler {
#Handler
public INTERACTION handle(Message message) throws IOException, MessageException {
checkMessageIsNotNull(message);
INVOCATION invocation = construct(message.getBody().toString());
l.debug("handling in initializer... {}", invocation);
return **new INTERACTION(invocation)**; //HERE!
}
public INVOCATION construct(String message) throws IOException, MessageException {
ObjectMapper mapper = new ObjectMapper();
INVOCATION invocation;
try {
invocation = mapper.readValue(message, **INVOCATION.class** /*<- and HERE */);
} catch (IOException e) {
throw new MessageException("Can't deserialize message", e);
}
return invocation;
}
}
But I have two compilers error and don't know how to bypass this issue. I mark them in code
Java generics are implemented with type erasure for compatibility reasons, which ultimately means you cannot use a mere type parameter to instantiate a new object.
What you'll need is to modify AbstractInitializer a bit...
private final Class<INTERACTION> interactionType;
private final Class<INVOCATION> invocationType;
private final Constructor<INTERACTION> interactionConstructor;
public AbstractInitializer(final Class<INTERACTION> interactionType,
final Class<INVOCATION> invocationType) throws NoSuchMethodException {
this.interactionType = interactionType;
this.invocationType = invocationType;
interactionConstructor = interactionType.getConstructor(invocationType);
}
public INTERACTION handle(Message message) throws IOException, MessageException,
InvocationTargetException, InstantiationException, IllegalAccessException {
checkMessageIsNotNull(message);
INVOCATION invocation = construct(message.getBody().toString());
l.debug("handling in initializer... {}", invocation);
return interactionConstructor.newInstance(invocation);
}
public INVOCATION construct(String message) throws IOException, MessageException {
ObjectMapper mapper = new ObjectMapper();
INVOCATION invocation;
try {
invocation = mapper.readValue(message, invocationType);
} catch (IOException e) {
throw new MessageException("Can't deserialize message", e);
}
return invocation;
}
then, as an e.g.
public final class PingInitializer
extends AbstractInitializer<PingInteraction, PingInvocation> {
public PingInitializer() {
super(PingInteraction.class, PingInvocation.class);
}
}
or, you could ditch making it abstract and use it like...
public static GenericInitializer<A, B> createInitializer(final Class<A> a,
final Class<B> b) {
return new GenericInitializer<A, B>(a, b);
}
final GenericInitializer<PingInteraction, PingInvocation> pingInitializer
= createInitializer(PingInteraction.class, PingInvocation.class);
I believe this should all be possible. I probably made some dumb errors, however, given I typed this in the response box in a hurry.
I've this enum:
enum RequestStatus {
OK(200), NOT_FOUND(400);
private final int code;
RequestStatus(int code) {
this.code = code;
}
public int getCode() {
return this.code;
}
};
and in my Request-class, I have this field: private RequestStatus status.
When using Gson to convert the Java object to JSON the result is like:
"status": "OK"
How can I change my GsonBuilder or my Enum object to give me an output like:
"status": {
"value" : "OK",
"code" : 200
}
You can use something like this:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapterFactory(new MyEnumAdapterFactory());
or more simply (as Jesse Wilson indicated):
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(RequestStatus.class, new MyEnumTypeAdapter());
and
public class MyEnumAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {
Class<? super T> rawType = type.getRawType();
if (rawType == RequestStatus.class) {
return new MyEnumTypeAdapter<T>();
}
return null;
}
public class MyEnumTypeAdapter<T> extends TypeAdapter<T> {
public void write(JsonWriter out, T value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
RequestStatus status = (RequestStatus) value;
// Here write what you want to the JsonWriter.
out.beginObject();
out.name("value");
out.value(status.name());
out.name("code");
out.value(status.getCode());
out.endObject();
}
public T read(JsonReader in) throws IOException {
// Properly deserialize the input (if you use deserialization)
return null;
}
}
}
In addition to Polet's answer, if you need a generic Enum serializer, you can achieve it via reflection:
public class EnumAdapterFactory implements TypeAdapterFactory
{
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type)
{
Class<? super T> rawType = type.getRawType();
if (rawType.isEnum())
{
return new EnumTypeAdapter<T>();
}
return null;
}
public class EnumTypeAdapter<T> extends TypeAdapter<T>
{
#Override
public void write(JsonWriter out, T value) throws IOException
{
if (value == null || !value.getClass().isEnum())
{
out.nullValue();
return;
}
try
{
out.beginObject();
out.name("value");
out.value(value.toString());
Arrays.stream(Introspector.getBeanInfo(value.getClass()).getPropertyDescriptors())
.filter(pd -> pd.getReadMethod() != null && !"class".equals(pd.getName()) && !"declaringClass".equals(pd.getName()))
.forEach(pd -> {
try
{
out.name(pd.getName());
out.value(String.valueOf(pd.getReadMethod().invoke(value)));
} catch (IllegalAccessException | InvocationTargetException | IOException e)
{
e.printStackTrace();
}
});
out.endObject();
} catch (IntrospectionException e)
{
e.printStackTrace();
}
}
public T read(JsonReader in) throws IOException
{
// Properly deserialize the input (if you use deserialization)
return null;
}
}
}
Usage:
#Test
public void testEnumGsonSerialization()
{
List<ReportTypes> testEnums = Arrays.asList(YourEnum.VALUE1, YourEnum.VALUE2);
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapterFactory(new EnumAdapterFactory());
Gson gson = builder.create();
System.out.println(gson.toJson(reportTypes));
}
I want to use jackson json library for a generic method as follows:
public MyRequest<T> tester() {
TypeReference<MyWrapper<T>> typeRef = new TypeReference<MyWrapper<T>>();
MyWrapper<T> requestWrapper = (MyWrapper<T>) JsonConverter.fromJson(jsonRequest, typeRef);
return requestWrapper.getRequest();
}
public class MyWrapper<T> {
private MyRequest<T> request;
public MyRequest<T> getRequest() {
return request;
}
public void setRequest(MyRequest<T> request) {
this.request = request;
}
}
public class MyRequest<T> {
private List<T> myobjects;
public void setMyObjects(List<T> ets) {
this.myobjects = ets;
}
#NotNull
#JsonIgnore
public T getMyObject() {
return myobjects.get(0);
}
}
Now the problem is that when I call getMyObject() which is inside the request object Jackson returns the nested custom object as a LinkedHashMap. Is there any way in which I specify that T object needs to be returned? For example: if I sent object of type Customer then Customer should be returned from that List?
This is a well-known problem with Java type erasure: T is just a type variable, and you must indicate actual class, usually as Class argument. Without such information, best that can be done is to use bounds; and plain T is roughly same as 'T extends Object'. And Jackson will then bind JSON Objects as Maps.
In this case, tester method needs to have access to Class, and you can construct
JavaType type = mapper.getTypeFactory().
constructCollectionType(List.class, Foo.class)
and then
List<Foo> list = mapper.readValue(new File("input.json"), type);
'JavaType' works !!
I was trying to unmarshall (deserialize) a List in json String to ArrayList java Objects and was struggling to find a solution since days.
Below is the code that finally gave me solution.
Code:
JsonMarshallerUnmarshaller<T> {
T targetClass;
public ArrayList<T> unmarshal(String jsonString) {
ObjectMapper mapper = new ObjectMapper();
AnnotationIntrospector introspector = new JacksonAnnotationIntrospector();
mapper.getDeserializationConfig()
.withAnnotationIntrospector(introspector);
mapper.getSerializationConfig()
.withAnnotationIntrospector(introspector);
JavaType type = mapper.getTypeFactory().
constructCollectionType(
ArrayList.class,
targetclass.getClass());
try {
Class c1 = this.targetclass.getClass();
Class c2 = this.targetclass1.getClass();
ArrayList<T> temp = (ArrayList<T>)
mapper.readValue(jsonString, type);
return temp ;
} catch (JsonParseException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null ;
}
}
I modified rushidesai1's answer to include a working example.
JsonMarshaller.java
import java.io.*;
import java.util.*;
public class JsonMarshaller<T> {
private static ClassLoader loader = JsonMarshaller.class.getClassLoader();
public static void main(String[] args) {
try {
JsonMarshallerUnmarshaller<Station> marshaller = new JsonMarshallerUnmarshaller<>(Station.class);
String jsonString = read(loader.getResourceAsStream("data.json"));
List<Station> stations = marshaller.unmarshal(jsonString);
stations.forEach(System.out::println);
System.out.println(marshaller.marshal(stations));
} catch (IOException e) {
e.printStackTrace();
}
}
#SuppressWarnings("resource")
public static String read(InputStream ios) {
return new Scanner(ios).useDelimiter("\\A").next(); // Read the entire file
}
}
Output
Station [id=123, title=my title, name=my name]
Station [id=456, title=my title 2, name=my name 2]
[{"id":123,"title":"my title","name":"my name"},{"id":456,"title":"my title 2","name":"my name 2"}]
JsonMarshallerUnmarshaller.java
import java.io.*;
import java.util.List;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
public class JsonMarshallerUnmarshaller<T> {
private ObjectMapper mapper;
private Class<T> targetClass;
public JsonMarshallerUnmarshaller(Class<T> targetClass) {
AnnotationIntrospector introspector = new JacksonAnnotationIntrospector();
mapper = new ObjectMapper();
mapper.getDeserializationConfig().with(introspector);
mapper.getSerializationConfig().with(introspector);
this.targetClass = targetClass;
}
public List<T> unmarshal(String jsonString) throws JsonParseException, JsonMappingException, IOException {
return parseList(jsonString, mapper, targetClass);
}
public String marshal(List<T> list) throws JsonProcessingException {
return mapper.writeValueAsString(list);
}
public static <E> List<E> parseList(String str, ObjectMapper mapper, Class<E> clazz)
throws JsonParseException, JsonMappingException, IOException {
return mapper.readValue(str, listType(mapper, clazz));
}
public static <E> List<E> parseList(InputStream is, ObjectMapper mapper, Class<E> clazz)
throws JsonParseException, JsonMappingException, IOException {
return mapper.readValue(is, listType(mapper, clazz));
}
public static <E> JavaType listType(ObjectMapper mapper, Class<E> clazz) {
return mapper.getTypeFactory().constructCollectionType(List.class, clazz);
}
}
Station.java
public class Station {
private long id;
private String title;
private String name;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return String.format("Station [id=%s, title=%s, name=%s]", id, title, name);
}
}
data.json
[{
"id": 123,
"title": "my title",
"name": "my name"
}, {
"id": 456,
"title": "my title 2",
"name": "my name 2"
}]