How to use Jackson ObjectMapper.readValue with generic class - java

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.

Related

Returning a Generic list from JsonNode using an Object Mapper?

I have the following code that returns a list of strings from a JsonNode:
public static List<String> asList(final JsonNode jsonNode) {
ObjectMapper mapper = new ObjectMapper();
return mapper.convertValue(jsonNode, ArrayList.class);
}
Example usage:
List<String> identities = Utils.asList(jsonNode);
I want to change this to use Generics to ensure that a JsonNode contain a list of any Type can also be converted and returned.
I have the below implementation (not uses Jackson ObjectMapper), but is this the optimal solution?
public static <T> List<T> asList(final JsonNode jsonNode) {
ObjectMapper mapper = new ObjectMapper();
return mapper.convertValue(jsonNode, ArrayList.class);
}
You can create a util method that accepts JSON string and TypeReference
public <T> T jsonMapper(String json, TypeReference<T> typeReference)
throws JsonParseException, JsonMappingException, IOException {
return objectMapper.readValue(json, typeReference);
}
For example you can call this method either to convert json string to List or single Object
List<String> lOfStr = jsonMapper(json,new TypeReference<List<String>>() { });
Employee emp = jsonMapper(json,new TypeReference<Employee>() { });
To avoid unchecked assignment you can use TypeReference:
public static <T> List<T> asList(final JsonNode jsonNode) {
ObjectMapper mapper = new ObjectMapper();
return mapper.convertValue(jsonNode, new TypeReference<List<T>>() {});
}

How to get use GSON.fromJson with type provided in a variable?

I wanted to use GSON.fromJSON in the following way:
Class<?> type = Abc.class;
Class<?> parametersObject = GSON.fromJson(parameters, type);
But I am getting compilation error on 2nd line. How do I infer the return type of the following line? I know it would be of Abc type in the above case. But I want to make this dynamic based on what is specified in type variable. How can I do that?
Try it with the below code. There is a question about the difference between ? and T in class and method signatures, link.
public class Tests<T> {
#Test
public void test01() {
Tests<String> stringTest= new Tests<>();
System.out.println(stringTest.parseObject("testtestt", String.class));
}
public T parseObject(String parameters, Class<T> clazz) {
Gson gson = new Gson();
T obj = (T) gson.fromJson(parameters, clazz);
return obj;
}
}
``
UPD: declare <T> T returning type of method.
#Test
public void test02() {
String stringstring = parseObject02("stringstring", String.class);
System.out.println(stringstring);
List list = parseObject02("[1,2,3]", List.class);
System.out.println(list);
// output in console
// stringstring
// [1.0, 2.0, 3.0]
}
public <T> T parseObject02(String parameters, Class<T> clazz) {
Gson gson = new Gson();
T obj = gson.fromJson(parameters, clazz);
return obj;
}

Convert JSON String to generic object in JAVA (with GSON)

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));
}

Java Generics and Super Type Tokens

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

GSON and Generic types

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

Categories

Resources