I am trying to make the three following methods into one generic solution, I tried some ideas which compile but don't do well at runtime.
public static List<User> parseToUsers(HttpResponse response) {
ObjectMapper mapper = new ObjectMapper();
String results = parseToString(response);
return mapper.readValue(results, new TypeReference<List<User>>() {});
}
public static List<Record> parseToRecords(HttpResponse response) {
ObjectMapper mapper = new ObjectMapper();
String results = parseToString(response);
return mapper.readValue(results, new TypeReference<List<Record>>() {});
}
public static Record parseToRecord(HttpResponse response) {
ObjectMapper mapper = new ObjectMapper();
String results = parseToString(response);
return mapper.readValue(results, new TypeReference<Record>() {});;
}
I have also tried to understand this blog post about Super Type Tokens.
EDIT:
This is what I came up with so far:
public static <T> T parseJsonResponse(TypeReference<T> type, HttpResponse response) throws DroidException {
ObjectMapper mapper = new ObjectMapper();
String results = parseResponseToString(response);
return readValue = mapper.readValue(results, type);
}
Then I call it like this.
parseJsonResponseToList(new TypeReference<List<Record>>() {}, response)
Not really satisfieng.Is there a better solution?
So what exactly is the problem? In what way do you not like it?
Jackson has other ways for constructing generic types; so perhaps what are looking for is along lines of:
public List<T> listOf(String json, Class<T> elementType) {
ObjectMapper mapper = new ObjectMapper(); // should (re)use static instance for perf!
JavaType listType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, elementType);
return mapper.readValue(json, listType);
}
TypeFactory can be used to programmatically construct types that use generics -- return type is JavaType, because basic Java Class is type-erased.
TypeFactory is actually used to convert TypeReference to JavaType internally as well.
EDIT
As to regular, non-Collection/Map types, it's really quite simple:
public T parseSingle(Class<T> cls, InputStream src) throws IOException {
return mapper.readValue(src, cls);
}
(you also do NOT want to read contents as String -- not only is it slow, but it's easy to mess up character encodings, so if possible, feed InputStream or byte[] instead)
I don't really know what your ObjectMapper and TypeReference classes do, so maybe this answer doesn't fit you all that well, but here's how I'd probably do it if I understand your situation at all:
public interface Parser<T> {
public T parse(String results);
public static class MapperParser<T> implements Parser<T> {
private final TypeReference<T> type;
public MapperParser(TypeReference<T> type) {this.type = type;}
public T parse(String results) {
return(new ObjectMapper().readValue(results, type));
}
}
public static final Parser<List<User>> users = new MapperParser(new TypeReference<List<User>>());
public static final Parser<List<Record>> records = new MapperParser(new TypeReference<List<Record>>());
public static final Parser<Record> record = new MapperParser(new TypeReference<Record>());
}
/* And then, in the class you were in your question: */
public static <T> T parseJsonResponse(Parser<T> parser, HttpResponse response) {
return(parser.parse(parseResponseToString(response)));
}
Then, you may call it as such:
parseJsonResponse(Parser.users, response)
Is that more to your liking?
Ok this is my favorite solution, inspired by Dolda2000, i keep as is in my initial post and add an enum.
public enum TypeRef {
RECORDS(new TypeReference<List<Record>>() {}), USERS(new TypeReference<List<User>>() {}), USER(new TypeReference<User>() {});
private TypeReference<?> type;
private TypeRef(TypeReference<?> type) {
this.type = type;
}
public TypeReference<?> getType() {
return this.type;
}
}
and then instead of writing:
readJsonResponse(new TypeReference<List<Record>>() {}, response)
i can write:
readJsonResponse(TypeRef.RECORDS, response);
no magic going on but i like it more than wrapping it in another interface
Ok after running into a compile error:
type parameters of T cannot be determined; no unique maximal instance exists for type variable T with upper bounds T,java.lang.Object
i quit the over engeneering session and keep it simple
private static TypeReference<List<Record>> RECORDS = new TypeReference<List<Record>>() {};
public static <T> T readJson(TypeReference<T> type, String text) {
ObjectMapper mapper = new ObjectMapper();
return readValue = mapper.readValue(text, type);
}
use it like this
readJson(RECORDS, text);
no enums, i just use static fields for the TypeReference and everyone can read the code easily without understanding TypeReference
thank you guys i learned something about over engeneering today :P
Related
how to use Jackson ObjectMapper.readValue with generic class, someone says that need JavaType, but JavaType is also splicing other class, is Jackson can use like gson TypeToken?
my code is like this
public static void main(String[] args) throws IOException {
String json = "{\"code\":200,\"msg\":\"success\",\"reqId\":\"d1ef3b76e73b40379f895a3a7f1389e2\",\"cost\":819,\"result\":{\"taskId\":1103,\"taskName\":\"ei_custom_config\",\"jobId\":233455,\"status\":2,\"interrupt\":false,\"pass\":true}}";
RestResponse<TaskResult> result = get(json);
System.out.println(result);
System.out.println(result.getResult().getJobId());
}
public static <T> RestResponse<T> get(String json) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(json, new TypeReference<RestResponse<T>>() {});
}
and error is
org.example.zk.RestResponse#6fd02e5
Exception in thread "main" java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to org.example.zk.TaskResult
at org.example.zk.JacksonTest.main(JacksonTest.java:15)
You need to provide jackson with concrete type information for T. I would suggest using readValue() overload with parameter - JavaType.
Add the class of T as parameter of get() and construct parametric type using it.
public static <T> RestResponse<T> get(String json, Class<T> classOfT) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
JavaType type = TypeFactory.defaultInstance().constructParametricType(RestResponse.class, classOfT);
return objectMapper.readValue(json, type);
}
Usage:
RestResponse<TaskResult> result = get(json, TaskResult.class);
We can make T with upper-bound to help infering object type.
public static <T extends TaskResult> RestResponse<T> get(String json) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(json, new TypeReference<RestResponse<T>>() {});
}
Without type bounduary, RestResponse<T> equals to RestResponse<Object>
We can not new a generic class with T.
I have a helper class calling a REST web service. This helper class receives the information from the web service within a class defined as a generic "R". I can easily use GSON to read my JSON in a POJO, but I can't figure out how to give it a List and read it properly.
So far I have tried to extract the type of R in a variable and pass it to the List without success. I have tried using Jackson with its TypeFactory, also without success, it won't take R as its parameter.
What I want to do is something like this :
resource = new GsonBuilder().create().fromJson(response,List<R>)
or with Jackson
resource = mapper.readValue(response, List<R>);
"response" being my JSON string and resource the instance of List I want to return.
I can use either GSON or Jackson. I have been trying with both since some things looked more possible with one or the other.
I was able to make it work with a non generic, but all I get with a generic is a list of list of Map of the JSON fields. It's basically ignoring the type of R.
Also, R is extended from a class called BaseResource, so I know R will always be a child of BaseResource, but the fields I need are in the child.
Bonus: my true end goal is to have my generic R be a list of a specific object. So if I could instead have :
resource = new GsonBuilder().create().fromJson(response,R)
and R can be either a BaseResource or a List. I already made it work for BaseResource, but I need it to make it work for List somehow either by my duplicating most of it and explicitely states it's a List or passing List to R.
Our current workaround is to have a wrapper that contains a list:
private class Result{
private List<BaseResource> result;
}
but we want the web service to return lists instead of wrappers like that.
With GSON you have to implement your own serializer see: Type Information within serialized JSON using GSON
If you were to use Jackson you can solve this using #JsonTypeInfo annotation
polymorphic types: https://www.logicbig.com/tutorials/misc/jackson/jackson-json-type-info-annotation.html
did not read if you use any framework, well if you use spring you can use restTemplate and Jackson, here is an example of a generic class to call a rest service
private <Q, R> R callGeneric(String url, Q query, Class<R> responseClass) {
R response = null;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Q> req = new HttpEntity<Q>(query, headers);
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
ResponseEntity<R> callResult = null;
try{
callResult = restTemplate.exchange(url, HttpMethod.POST, req, responseClass);
}catch(HttpClientErrorException e){
logger.error(e.getStatusCode());
logger.error(e.getResponseBodyAsString());
throw e;
}
if (callResult == null) {
logger.error("callGeneric: No response (void) of the rest service");
return null;
}
response = callResult.getBody();
if (response == null) {
logger.error("callGeneric: empty response) ");
return null;
}
return response;
}
public ObjectResponse setCustCommunicationSetup(objectRequest request) {
String url = restCommunicationHostServer + custCommunicationContext + custCommunicationServiceSetCommunicationSetup;
logger.debug("callGeneric: "+url);
return callGeneric(url, request, ObjectResponse.class);
}
a similar problem is described hear maybe it will still help you
You must create correctly Type if you want deserialize List of generic
public static <T> List<T> deserialize(String json, Class<T> clazz) {
Type type = TypeToken.getParameterized(List.class,clazz).getType();
return new Gson().fromJson(json, type);
}
OR
Type myType = new TypeToken<List<DTO1>>() {}.getType();
List<DTO1> deserialize = new Gson().fromJson(json, myType);
OR
Yours workaround -- for me is good
problem is T because Java does not know what i kind and generate Type of T
public static <T> List<T> sec(String json, Class<T> clazz) {
Type type1 = new TypeToken<List<T>>() {}.getType();
Type type2 = new TypeToken<List<DTO1>>() {}.getType();
Type type = TypeToken.getParameterized(List.class, clazz).getType();
System.out.println(type1); //==>....*List<T>
System.out.println(type2); //==>....*List<....DTO1> --> this declaration In Java
System.out.println(type); //==>....*List<....DTO1>
return new Gson().fromJson(json, type);
}
Testing
this is test for more example to correct run
package pl.jac.litsofgeneriric;
import java.lang.reflect.Type;
import java.util.List;
import java.util.UUID;
import org.junit.Assert;
import org.junit.Test;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
public class DeserializeListGenericTest {
#Test
public void deserializeDTO1() {
//given
String json = "[{\"id\":1,\"name\":\"test1\"},{\"id\":2,\"name\":\"test2\"}]";
//when
List<DTO1> deserialize = DeserializeListGeneric.deserialize(json, DTO1.class);
//then
Assert.assertEquals("test1", deserialize.get(0).name);
}
#Test
public void deserializeDTO2() {
//given
String json = "[{\"uuid\":\"97e98a6d-aae8-42cb-8731-013c207ea384\",\"name\":\"testUUID1\"},{\"uuid\":\"36c60080-7bb2-4c71-b4d8-c5a0a5fa47a2\",\"name\":\"testUUID1\"}]";
//when
List<DTO2> deserialize = DeserializeListGeneric.deserialize(json, DTO2.class);
//then
Assert.assertEquals("testUUID1", deserialize.get(0).name);
}
#Test
public void deserializeMyType() {
//given
String json = "[{\"id\":1,\"name\":\"test1\"},{\"id\":2,\"name\":\"test2\"}]";
//when
Type myType = new TypeToken<List<DTO1>>() {
}.getType();
List<DTO1> deserialize = new Gson().fromJson(json, myType);
//then
Assert.assertEquals("test1", deserialize.get(0).name);
}
#Test
public void deserializeGsonBuilder() {
//given
String json = "[{\"id\":1,\"name\":\"test1\"},{\"id\":2,\"name\":\"test2\"}]";
//when
Type myType = new TypeToken<List<DTO1>>() {
}.getType();
List<DTO1> deserialize = new GsonBuilder().create().fromJson(json, myType);
//then
Assert.assertEquals("test1", deserialize.get(0).name);
}
#Test
public void useSystemOut() {
//given
String json = "[{\"id\":1,\"name\":\"test1\"},{\"id\":2,\"name\":\"test2\"}]";
//when
List<DTO1> deserialize = sec(json, DTO1.class);
//then
Assert.assertEquals("test1", deserialize.get(0).name);
}
public static <T> List<T> sec(String json, Class<T> clazz) {
Type type1 = new TypeToken<List<T>>() {}.getType();
Type type2 = new TypeToken<List<DTO1>>() {}.getType();
Type type = TypeToken.getParameterized(List.class, clazz).getType();
System.out.println(type1); //==>....*List<T>
System.out.println(type2); //==>....*List<....DTO1> --> this declaration In Java
System.out.println(type); //==>....*List<....DTO1>
return new Gson().fromJson(json, type);
}
public static class DTO1 {
public Long id;
public String name;
}
public static class DTO2 {
public UUID uuid;
public String name;
}
}
and class DeserializeListGeneric
package pl.jac.litsofgeneriric;
import java.lang.reflect.Type;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
public class DeserializeListGeneric {
public static <T> List<T> deserialize(String json, Class<T> clazz) {
Type type = TypeToken.getParameterized(List.class,clazz).getType();
return new Gson().fromJson(json, type);
}
}
I have classes like
public class ClassA{
private String id;
private List<ClassB> bClass;
}
public class ClassB{
private int id;
}
public class ClassC{
private String id;
private List<ClassD> dClass;
}
public class ClassD{
private String name;
}
I am reading a json from a source which I can deserialize into a ClassA or ClassC object. I am planning to write a custom deserializer for this task like this:
public class StringToObjectDeserializer extends StdDeserializer<List<B>> {
public StringToObjectDeserializer() {
this(null);
}
public StringToObjectDeserializer(Class<?> vc) {
super(vc);
}
#Override
public List<B> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode jsonObject = p.getCodec().readTree(p);
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(jsonObject.textValue(), new TypeReference<List<B>>() {
});
}
}
As you can see that I am typing the return object of the deserializer as List<B>. What I would like is to have a single deserializer which would return List<B> or List<D> depending on the object property that is getting deserialized into. This is because both the deserializers have the same logic, only difference being the typing. I know that type erasure happens at runtime but is there any way around this?
I have tried public class StringToObjectDeserializer extends StdDeserializer<List> as the signature, but it only returns a hash map which fails to map to the list of objects.
I want to use the deserializer by registering a SimpleModule like:
SimpleModule deSerializerModule = new SimpleModule();
deSerializerModule.addDeserializer(<what to put here?>, new StringToObjectDeserializer());
ObjectMapper mapper = new ObjectMapper()
.registerModule(deSerializerModule);
List<A> listA = mapper.readValue(mapper.writeValueAsString(readJson), new TypeReference<List<A>>() {});
Any help is appreciated!
EDIT:
The reason I require a custom deserializer is due to the need of parsing 2 different types of json inputs:
Type 1:
{
"id":"abc123",
"bClass":[{
"id":123
},{
"id":456
}]
}
Type 2:
{
"id":"abc123",
"bClass":"[{\"id\":123},{\"id\":456}]"
}
Type 1 can be parsed easily by Jackson itself and I am indeed doing so in a place but handling Type 2 is the challenge as it is a string representation of a json array.
Currently I have a hack in place which manually converts the input from Type 2 to Type 1 before putting it through the mapper but I don't find it elegant.
I have an Api that returns JSON. The response is in some format that can fit into an object called ApiResult and contains a Context <T> and an int Code.
ApiResult is declared in a generic way, e.g. ApiResult<SomeObject>
I would like to know how to get GSON to convert the incoming JSON String to ApiResult<T>
So far I have:
Type apiResultType = new TypeToken<ApiResult<T>>() { }.getType();
ApiResult<T> result = gson.fromJson(json, apiResultType);
But this still returns converts the Context to a LinkedHashMap instead (which I assume its what GSON falls back to)
You have to know what T is going to be. The incoming JSON is fundamentally just text. GSON has no idea what object you want it to become. If there's something in that JSON that you can clue off of to create your T instance, you can do something like this:
public static class MyJsonAdapter<X> implements JsonDeserializer<ApiResult<X>>
{
public ApiResult<X> deserialize( JsonElement jsonElement, Type type, JsonDeserializationContext context )
throws JsonParseException
{
String className = jsonElement.getAsJsonObject().get( "_class" ).getAsString();
try
{
X myThing = context.deserialize( jsonElement, Class.forName( className ) );
return new ApiResult<>(myThing);
}
catch ( ClassNotFoundException e )
{
throw new RuntimeException( e );
}
}
}
I'm using a field "_class" to decide what my X needs to be and instantiating it via reflection (similar to PomPom's example). You probably don't have such an obvious field, but there has to be some way for you to look at the JsonElement and decide based on what's itn it what type of X it should be.
This code is a hacked version of something similar I did with GSON a while back, see line 184+ at: https://github.com/chriskessel/MyHex/blob/master/src/kessel/hex/domain/GameItem.java
You have to provide Gson the type of T. As gson doesn't know what adapter should be applied, it simply return a data structure.
Your have to provide the generic, like :
Type apiResultType = new TypeToken<ApiResult<String>>() { }.getType();
If type of T is only known at runtime, I use something tricky :
static TypeToken<?> getGenToken(final Class<?> raw, final Class<?> gen) throws Exception {
Constructor<ParameterizedTypeImpl> constr = ParameterizedTypeImpl.class.getDeclaredConstructor(Class.class, Type[].class, Type.class);
constr.setAccessible(true);
ParameterizedTypeImpl paramType = constr.newInstance(raw, new Type[] { gen }, null);
return TypeToken.get(paramType);
}
Your call would be (but replacing String.class with a variable) :
Type apiResultType = getGenToken(ApiResult.class, String.class);
My solution is using org.json and Jackson
Below are the methods to wrap a json object into an array, to convert an object to into a list and to convert json string to a type.
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
public <T> List<T> parseJsonObjectsToList(JSONObject parentJson, String key, Class<T> clazz) throws IOException {
Object childObject = parentJson.get(key);
if(childObject == null) {
return null;
}
if(childObject instanceof JSONArray) {
JSONArray jsonArray = parentJson.getJSONArray(key);
return getList(jsonArray.toString(), clazz);
}
JSONObject jsonObject = parentJson.getJSONObject(key);
List<T> jsonList = new ArrayList<>();
jsonList.add(getObject(jsonObject.toString(), clazz));
return jsonList;
}
public <T> List<T> getList(String jsonStr, Class clazz) throws IOException {
ObjectMapper objectMapper = OBJECT_MAPPER;
TypeFactory typeFactory = objectMapper.getTypeFactory();
return objectMapper.readValue(jsonStr, typeFactory.constructCollectionType(List.class, clazz));
}
public <T> T getObject(String jsonStr, Class<T> clazz) throws IOException {
ObjectMapper objectMapper = OBJECT_MAPPER;
return objectMapper.readValue(jsonStr, clazz);
}
// To call
parseJsonObjectsToList(creditReport, JSON_KEY, <YOU_CLASS>.class);
I use JacksonJson library, quite similar to GSon. It's possible to convert json string to some generic type object this way:
String data = getJsonString();
ObjectMapper mapper = new ObjectMapper();
List<AndroidPackage> packages = mapper.readValue(data, List.class);
Maybe this is correct way with GSON in your case:
ApiResult<T> result = gson.fromJson(json, ApiResult.class);
JSON to generic object
public <T> T fromJson(String json, Class<T> clazz) {
return new Gson().fromJson(json, clazz);
}
JSON to list of generic objects
public <T> List<T> fromJsonAsList(String json, Class<T[]> clazz) {
return Arrays.asList(new Gson().fromJson(json, clazz));
}
I've come across a problem of using Gson library and generic types(my types and collections). However they have an answer how to solve this problem, I don't think it's appropriate to write a specific message converter for the every type I've already implemented and I'll implement.
What I did is:
Implemented my own message converter:
public class SuperHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
private final Charset charset;
private final Gson gson;
public CostomHttpMC_1(MediaType mediaType, String charset) {
super(mediaType);
this.charset = Charset.forName(charset);
gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
}
#Override
protected Object readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException {
String jsonString = FileCopyUtils.copyToString(new InputStreamReader(inputMessage.getBody(), charset));
return gson.fromJson(jsonString, clazz);
}
#Override
protected Long getContentLength(Object obj, MediaType contentType) {
try {
String jsonString = gson.toJson(obj);
return (long) jsonString.getBytes(charset.name()).length;
} catch (UnsupportedEncodingException ex) {
throw new InternalError(ex.getMessage());
}
}
#Override
protected void writeInternal(Object obj, HttpOutputMessage outputMessage) throws IOException {
String jsonString = gson.toJson(obj);
FileCopyUtils.copy(jsonString, new OutputStreamWriter(outputMessage.getBody(), charset));
}
#Override
public boolean supports(Class<?> clazz) {
return true;
}
}
It works well until I try to send a collection like List<String> or some Type<T>.
Gson has the solutions here: http://sites.google.com/site/gson/gson-user-guide
Also I tried the json-lib library yesterday. What I don't like about it is in-depth scanning of all objects which I have in the hierarchy. I tried to change the cycle detection strategy from CycleDetectionStrategy.STRICT to CycleDetectionStrategy.LENIENT, it didn't help at all!
#Override
protected void writeInternal(Object obj, HttpOutputMessage outputMessage) throws IOException {
JsonConfig jsonConfig = new JsonConfig();
jsonConfig.setCycleDetectionStrategy(CycleDetectionStrategy.LENIENT);
String jsonString = JSONObject.fromObject( obj ).toString();
FileCopyUtils.copy(jsonString, new OutputStreamWriter(outputMessage.getBody(), charset));
}
Finally, a work-around for the generic collection's problem was found out: changing from ArrayList to simple array helps to do serialization and deserialization. To be more specific you have to do it in a web-service, which you use in an application.
#RequestMapping(value = "/country/info/{code}")
public void info(#PathVariable("code") String code, Model model) {
//list
StuffImpl[] stuffList= new StuffImpl[0]; <-- this is the array I used!
stuffList= restTemplate.getForObject("http://localhost:8084/yourApp/restService/stuff", stuffList.getClass());
model.addAttribute("stuffList", stuffList);
}
So this approach is working good.
I failed to found out what a solution for generic type is. I really do hate an idea to write a new converter every time I implement a new generic type.
If you know any possible solution I'd appreciate your help a lot!
I'd be on the cloud nine if anyone could help me :)
L.
There are some methods where you can pass java.lang.reflect.Type. These methods are useful if the specified object is a generic type, e.g.:
Gson gson = new GsonBuilder().create();
List<String> names = new ArrayList<String>();
names.add("Foo");
names.add("Bar");
// marshal
String jsonLiteral = gson.toJson(names);
System.out.println(jsonLiteral);
// unmarshal
List<String> names2;
Type type = new TypeToken<List<String>>() {
}.getType();
names2 = gson.fromJson(jsonLiteral, type);
System.out.println(names2.get(0));
System.out.println(names2.get(1));
This will output:
["Foo","Bar"]
Foo
Bar