15

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)

5 Answers 5

5
+25

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

Sign up to request clarification or add additional context in comments.

2 Comments

I will check the code and I suspect that I had to do something like you suggested but the same (or similar) code in .NET works. So that means I have to change a generic API to cater for a specific language? .NET can find at runtime what T is without me having to have a special _class field in a json.
Without knowing the .NET code, I have no idea how that'd work. T has no class value so .NET wouldn't know what class to use unless something tells it.
2

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

2 Comments

So I take that T is of type String in your example. But I dont have a String but a T. How will the last statement (the assignment) change?
Yep, I was assuming you have a Class<T> var to set in place of String.class (in getGenToken). But maybe that doesn't apply to your case.
2

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

Comments

2

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

2 Comments

how to call fromJsonAsList method?
fromJsonAsList(json, SomeClass[].class)
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.