nested generic parameter as a method parameter - Java - java

Based on this question How to get a class instance of generics type T I have implemented the following class:
public class OkJsonConverter<T> {
final Class<T> typeParameterClass;
public OkJsonConverter(Class<T> typeParameterClass) {
this.typeParameterClass = typeParameterClass;
}
protected T processJson(String json) throws OkClientException {
T object = null;
ObjectMapper objectMapper = new ObjectMapper();
try {
JsonNode jsonNode = objectMapper.readTree(json);
if (jsonNode.get("error_code") != null) {
Error error = objectMapper.treeToValue(jsonNode, Error.class);
throw new OkClientException("API returned error", error);
} else {
object = objectMapper.treeToValue(jsonNode, typeParameterClass);
}
} catch (IOException e) {
throw new OkClientException("unable to process json", e);
}
return object;
}
}
I can use this class with a generic parameters, for example:
return new OkJsonConverter<User>(User.class).processJson(response.getBody());
but right now I'm struggling how to make it working with a nested generic parameter like this one List<Method>
This code doesn't work:
return new OkJsonConverter<List<Method>>(List<Method>.class).processJson(response.getBody());
Please help to change this code in order to get it working.

Java doesn't have any way to represent that type as a Class. The closest approximation you can get is (Class<List<Method>>) (Class) List.class, but that cast just papers over that you're just looking at a basic List that doesn't know its element type.
Whether or not that works with your JSON converter isn't clear, but should be specified in the documentation of the converter you're using, which will have to deal with this itself, since this is a universal problem in Java when you're trying to reflect on generic types.

Finally, thanks to user3707125 I have found a way how to implement this:
Corrected by idierL
public class OkJsonConverter {
private static final String ERROR_CODE_FIELD_NAME = "error_code";
protected <T> T readTreeToValue(String json, TypeReference<T> valueTypeRef) throws OkClientException {
T object = null;
ObjectMapper objectMapper = new ObjectMapper();
try {
JsonNode jsonNode = objectMapper.readTree(json);
if (jsonNode.has(ERROR_CODE_FIELD_NAME)) {
Error error = objectMapper.treeToValue(jsonNode, Error.class);
throw new OkClientException("Ok API returned error", error);
} else {
JavaType type = objectMapper.getTypeFactory().constructType(valueTypeRef);
object = objectMapper.convertValue(jsonNode, type);
}
} catch (IOException e) {
throw new OkClientException("Unable to process JSON", e);
}
return object;
}
}
Now, the following code works fine:
List<Method> result = new OkJsonConverter().readTreeToValue(response.getBody(), new TypeReference<List<Method>>() {});

Related

I have two exact same Java method and I want to create a generic one, how should I do it?

private void extracted(Principal principal, UnoBorrowerStaging unoBorrowerStaging, Integer status, String remarks,
UnoBorrowerStagingRepo unoBorrowerStagingRepo, UnoBorrowerDetails unoBorrowerDetails) {
ObjectMapper objectMapper = new ObjectMapper();
String unoBorrowerDetailsAsString = null;
try {
unoBorrowerDetailsAsString = objectMapper.writeValueAsString(unoBorrowerDetails);
} catch (JsonProcessingException e1) {
e1.printStackTrace();
}
unoBorrowerStaging.setIs_processed(status);
unoBorrowerStaging.setRemarks(remarks);
unoBorrowerStaging.setPayload(unoBorrowerDetailsAsString);
unoBorrowerStaging.setCreatedBy(principal.getName());
unoBorrowerStagingRepo.save(unoBorrowerStaging);
}
private void extracted2(Principal principal, UnoDocumentsStaging unoDocumentsStaging, Integer status,
String remarks, UnoDocumentsStagingRepo unoDocumentsStagingRepo, UnoDocumentsEntity unoDocumentsEntity) {
ObjectMapper objectMapper = new ObjectMapper();
String jsonString = null;
try {
jsonString = objectMapper.writeValueAsString(unoDocumentsEntity);
} catch (JsonProcessingException e1) {
e1.printStackTrace();
}
unoDocumentsStaging.setIs_processed(status);
unoDocumentsStaging.setRemarks(remarks);
unoDocumentsStaging.setPayload(jsonString);
unoDocumentsStaging.setCreatedBy(principal.getName());
unoDocumentsStagingRepo.save(unoDocumentsStaging);
}
These two method bodies are the same, but the arguments are different like the entity class. I want to create one generic method where I pass the object and create instance of those passing parameters, in that way I don't have to create separate methods.
The above code is for parsing JSON into string then insert al the details into a database.
You can just use type Object instead of UnoDocumentsEntity and UnoBorrowerDetails.
Look at the signature of the method ObjectMapper#writeValueAsString(Object value)
https://fasterxml.github.io/jackson-databind/javadoc/2.7/com/fasterxml/jackson/databind/ObjectMapper.html#writeValueAsString(java.lang.Object)

How can i deserialize complex List like List<List<Object>> using Java reflection and Jackson like SpringMVC #RequestBody do

I don't know if it's clear for you, I want to implement a method like SpringMVC's #RequestBody(), this method can deserialize input json string to object, and this method should support complex object type(like List<List<Object>>)
Now using Java reflection and Jackson, we can easily deserialize List<Object>, such as "[1,100]" to List<Long>, but if the input is "[[1,100]]", there is nested list in another list.
Here is my code:
// there is a class and method's parameter has annotation #Param
class Code{
public void getName(#Param("fooList") List<List<Long>> fooList) {
System.out.println("getName run");
}
}
// reflection Code#getName and deserialize method
public Object inject(Parameter param) {
Object object = null;
try {
Class<?> paramType = param.getType();
Param paramAnnotation = param.getAnnotation(Param.class);
// for unit test purpose, hard code json string here
object = "[[1,3]]";
if (object != null) {
if (Collection.class == paramType || List.class.isAssignableFrom(paramType)) {
// basic type
if (clazz != null) {
// todo: string to list, need support complex List
try {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(text,
objectMapper.getTypeFactory().constructCollectionType(List.class,
clazz));
} catch (Exception e) {
throw new SDKException("");
}
List list = JSONs.toList(object.toString(), clazz);
List converterArgs = new ArrayList<>(list.size());
for (Object arg : list) {
try {
arg = Casts.cast(clazz, arg);
} catch (Exception e) {
throw new InvalidParametersException("type error");
}
converterArgs.add(arg);
}
object = converterArgs;
}
}
if (String.class.isAssignableFrom(paramType)) {
object = JSONs.string(object);
} else {
if (!object.getClass().equals(paramType)) {
try {
object = Casts.cast(paramType, object);
} catch (Exception e) {
throw new InvalidParametersException("type error");
}
}
}
}
} catch (Exception e) {
throw new SDKException("", e);
}
return object;
}
#Test
public void testReflection() {
try {
Method getName = Code.class.getMethod("getName", List.class);
Parameter[] parameters = getName.getParameters();
AnnotationParamInjector injector = new AnnotationParamInjector();
Object inject = injector.inject(parameters[0]);
System.out.println("inject = " + inject);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
Backgroud: I want to implement a method like SpringMVC's controller, so I need to deserialize a method parameter to an object. Right now there is only support for a normal class and a List but none for a complex List.
Questions:
Java reflection can parse generic class, even complex List, we can get reflect List from outer to inner, but how can I parse say a json string "[[1, 100]]"? This is the problem that I'm trying to solve.
Here is my pseudocode, I recursively call constructClass() to get its generic row type, after calling constructClass(), the json string should be parsed, ie something like "[[1,100]]" should change to "[1,100]"
public void constructClass(String text, Class<?> clazz) {
Type type = clazz.getGenericSuperclass();
if (Collection.class == clazz || List.class.isAssignableFrom(clazz)) {
if (type instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) type;
Type genericType = pType.getActualTypeArguments()[0];
this.constructClass(text, genericType.getClass());
}
} else if (Map.class == clazz) {
} else {
}
if (type instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) type;
pType.getActualTypeArguments()[0]
}
}
Could someone provide a good way to do this?

STRING to ArrayList<String> using TypeReference

str = "[tdr1w6v, tdr1w77]";
ObjectMapper objectMapper = new ObjectMapper();
JavaType type = objectMapper.getTypeFactory().
constructCollectionType(ArrayList.class, String.class);
ArrayList<String> list = null;
try {
list = objectMapper.readValue(str,
new TypeReference<ArrayList<String>>(){});
} catch (IOException e) {
e.printStackTrace();
}
Here an exception is thrown :
com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'tdr1w6v': was expecting 'null', 'true', 'false' or NaN
How can I convert str to ArrayList of string ?
#FedericoPeraltaSchaffner suggestion helped. Now what I do is, in my binder class use objectMapper.writeValueAsString to convert data to store in database. And in my Mapper class while reading from data base I can use the same way as in the question:
ObjectMapper objectMapper = new ObjectMapper();
ArrayList<String> list = null;
try {
list =objectMapper.readValue(str, new TypeReference<ArrayList<String>>(){});
} catch (IOException e) {
e.printStackTrace();
}
So now I don't have to create a separate DTO class, I can use the same model at service layer and DAO.
The requirement can be easily met without using TypeReference
String str="[tdr1w6v, tdr1w77]";
List<String> al=Arrays.asList(str.replaceAll("[\\[\\]]","").split(","));
System.out.println(al);

Converting POJO to JsonNode using a JsonView

I'm writing a typical Play Framework app where I want to return a JsonNode from my Controller's methods, using Jackson.
This is how I'm doing it right now:
public static Result foo() {
MyPojoType myPojo = new myPojo();
String tmp = new ObjectMapper().writerWithView(JSONViews.Public.class).writeValueAsString(myPojo);
JsonNode jsonNode = Json.parse(tmp);
return ok(jsonNode);
}
Is it possible to avoid the "String tmp" copy and convert directly from MyPojoType to JsonNode using a view?
Maybe I can use ObjectMapper.valueToTree, but I don't know how to specify a JSonView to it.
Interesting question: off-hand, I don't think there is a specific method, and your code is the most straight-forward way to do it: valueToTree method does not apply any views.
So code is fine as is.
After more investigation, this is what I did in the end to avoid the redundant work:
public Result toResult() {
Content ret = null;
try {
final String jsonpayload = new ObjectMapper().writerWithView(JsonViews.Public.class).writeValueAsString(payload);
ret = new Content() {
#Override public String body() { return jsonpayload; }
#Override public String contentType() { return "application/json"; }
};
} catch (JsonProcessingException exc) {
Logger.error("toResult: ", exc);
}
if (ret == null)
return Results.badRequest();
return Results.ok(ret);
}
In summary: The methods ok, badRequest, etc accept a play.mvc.Content class. Then, simply use it to wrap your serialized json object.
As i know, with jax-rs, you can do this :
public Response toResult() throws JsonProcessingException {
final ObjectWriter writer = new ObjectMapper()
.writerWithView(JSONViews.Public.class);
return Response.ok(new StreamingOutput() {
#Override
public void write(OutputStream outputStream) throws IOException, WebApplicationException {
writer.writeValue(outputStream, /*Pojo*/ payload);
}
}).build();
}
So you have to find a class in the Play framework which able to stream the result (through an OutputStream)
I think this is more efficient way
public Result toResult() {
MyPojo result = new MyPojo();
JsonNode node = objectMapper.valueToTree(result);
return ok(node);
}

Deserialize JSON to ArrayList<POJO> using Jackson

I have a Java class MyPojo that I am interested in deserializing from JSON. I have configured a special MixIn class, MyPojoDeMixIn, to assist me with the deserialization. MyPojo has only int and String instance variables combined with proper getters and setters. MyPojoDeMixIn looks something like this:
public abstract class MyPojoDeMixIn {
MyPojoDeMixIn(
#JsonProperty("JsonName1") int prop1,
#JsonProperty("JsonName2") int prop2,
#JsonProperty("JsonName3") String prop3) {}
}
In my test client I do the following, but of course it does not work at compile time because there is a JsonMappingException related to a type mismatch.
ObjectMapper m = new ObjectMapper();
m.getDeserializationConfig().addMixInAnnotations(MyPojo.class,MyPojoDeMixIn.class);
try { ArrayList<MyPojo> arrayOfPojo = m.readValue(response, MyPojo.class); }
catch (Exception e) { System.out.println(e) }
I am aware that I could alleviate this issue by creating a "Response" object that has only an ArrayList<MyPojo> in it, but then I would have to create these somewhat useless objects for every single type I want to return.
I also looked online at JacksonInFiveMinutes but had a terrible time understanding the stuff about Map<A,B> and how it relates to my issue. If you cannot tell, I'm entirely new to Java and come from an Obj-C background. They specifically mention:
In addition to binding to POJOs and "simple" types, there is one
additional variant: that of binding to generic (typed) containers.
This case requires special handling due to so-called Type Erasure
(used by Java to implement generics in somewhat backwards compatible
way), which prevents you from using something like
Collection.class (which does not compile).
So if you want to bind data into a Map you will need to use:
Map<String,User> result = mapper.readValue(src, new TypeReference<Map<String,User>>() { });
How can I deserialize directly to ArrayList?
You can deserialize directly to a list by using the TypeReference wrapper. An example method:
public static <T> T fromJSON(final TypeReference<T> type,
final String jsonPacket) {
T data = null;
try {
data = new ObjectMapper().readValue(jsonPacket, type);
} catch (Exception e) {
// Handle the problem
}
return data;
}
And is used thus:
final String json = "";
Set<POJO> properties = fromJSON(new TypeReference<Set<POJO>>() {}, json);
TypeReference Javadoc
Another way is to use an array as a type, e.g.:
ObjectMapper objectMapper = new ObjectMapper();
MyPojo[] pojos = objectMapper.readValue(json, MyPojo[].class);
This way you avoid all the hassle with the Type object, and if you really need a list you can always convert the array to a list by:
List<MyPojo> pojoList = Arrays.asList(pojos);
IMHO this is much more readable.
And to make it be an actual list (that can be modified, see limitations of Arrays.asList()) then just do the following:
List<MyPojo> mcList = new ArrayList<>(Arrays.asList(pojos));
This variant looks more simple and elegant.
//import com.fasterxml.jackson.core.JsonProcessingException;
//import com.fasterxml.jackson.databind.ObjectMapper;
//import com.fasterxml.jackson.databind.type.CollectionType;
//import com.fasterxml.jackson.databind.type.TypeFactory;
//import java.util.List;
CollectionType typeReference =
TypeFactory.defaultInstance().constructCollectionType(List.class, Dto.class);
List<Dto> resultDto = objectMapper.readValue(content, typeReference);
This works for me.
#Test
public void cloneTest() {
List<Part> parts = new ArrayList<Part>();
Part part1 = new Part(1);
parts.add(part1);
Part part2 = new Part(2);
parts.add(part2);
try {
ObjectMapper objectMapper = new ObjectMapper();
String jsonStr = objectMapper.writeValueAsString(parts);
List<Part> cloneParts = objectMapper.readValue(jsonStr, new TypeReference<ArrayList<Part>>() {});
} catch (Exception e) {
//fail("failed.");
e.printStackTrace();
}
//TODO: Assert: compare both list values.
}
I am also having the same problem. I have a json which is to be converted to ArrayList.
Account looks like this.
Account{
Person p ;
Related r ;
}
Person{
String Name ;
Address a ;
}
All of the above classes have been annotated properly.
I have tried TypeReference>() {}
but is not working.
It gives me Arraylist but ArrayList has a linkedHashMap which contains some more linked hashmaps containing final values.
My code is as Follows:
public T unmarshal(String responseXML,String c)
{
ObjectMapper mapper = new ObjectMapper();
AnnotationIntrospector introspector = new JacksonAnnotationIntrospector();
mapper.getDeserializationConfig().withAnnotationIntrospector(introspector);
mapper.getSerializationConfig().withAnnotationIntrospector(introspector);
try
{
this.targetclass = (T) mapper.readValue(responseXML, new TypeReference<ArrayList<T>>() {});
}
catch (JsonParseException e)
{
e.printStackTrace();
}
catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return this.targetclass;
}
I finally solved the problem. I am able to convert the List in Json String directly to ArrayList as follows:
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 ;
}
}

Categories

Resources