简体   繁体   中英

Deserialize Generic class Jackson or Gson

From the land of .NET I have a generic class define like so..

public class SyncWrapper<T, I>
{
    public IList<T> Data { get; set; }
    public IList<I> DeleteIds { get; set; }
    public DateTime LastSyncDateTime { get; set; }
}

I was able to create an instance of this object from json by simply calling ...

JsonConvert.DeserializeObject<SyncWrapper<T, Guid>>(json);

Now I've been given the task of porting this code over to Java/Android. Having never touched Java before, I've a lot to learn!

Anyway, so far I've tried Gson and Jackson to get the object from json but no joy. I think that I won't be able to call andthing with the <T> involved gson.fromJson(json, SyncWrapper<T, UUID>.class) for example as there is a problem with type Erasure!

My efforts so far have looked like this....

Gson

Gson gson = new Gson();

SyncWrapper<MyClass, UUID> result = gson.fromJson(json, new TypeToken<SyncWrapper<MyClass, UUID>>() { }.getType());

This compiles but the result is an empty SyncWrapper

Jackson

ObjectMapper mapper = new ObjectMapper();

SyncWrapper<MyClass, UUID> result = mapper.readValue(json, new TypeReference<SyncWrapper<MyClass, UUID>>() { });

This compiles but crashes the app when executed!!!

My Java version of SyncWrapper....

public class SyncWrapper<T, I> {

    private DateTime lastSyncDateTime;
    private Collection<T> data;
    private Collection<I> deleteIds;

    public Collection<T> getData() {
        return data;
    }

    public void setData(Collection<T> data) {
        this.data = data;
    }

    public Collection<I> getDeleteIds() {
        return deleteIds;
    }

    public void setDeleteIds(Collection<I> deleteIds) {
        this.deleteIds = deleteIds;
    }

    public DateTime getLastSyncDateTime() {
        return lastSyncDateTime;
    }

    public void setLastSyncDateTime(DateTime lastSyncDateTime) {
        this.lastSyncDateTime = lastSyncDateTime;
    }
}

I've been really thrown in at the deep end by the powers that be (all programming is the same isn't it?), so any help really appreciated.

I'm not precious about which library I use (Gson, Jackson, etc)

Update

An example of the Json that is to be deserialized...

{
  "Data": [
    {
      "Name": "Company A",
      "Id": "7d5d236c-c2b5-42dc-aea5-99e6752c8a52"
    },
    {
      "Name": "Company B",
      "Id": "44444444-0000-0000-0000-444444444444"
    },
    {
      "Name": "Company C",
      "Id": "249a4558-05c6-483f-9835-0056804791c9"
    }
  ],
  "DeleteIds": [
    "5f7873a6-b2ee-4566-9714-1577b81384f4",
    "1f224a39-16c3-441d-99de-8e58fa8f31c2"
  ],
  "LastSyncDateTime": "\/Date(1393580073773+0000)\/"
}

..or this (more often than not, the DeleteIds will be null)...

{
  "Data": [
    {
      "Name": "Company A",
      "Id": "7d5d236c-c2b5-42dc-aea5-99e6752c8a52"
    },
    {
      "Name": "Company B",
      "Id": "44444444-0000-0000-0000-444444444444"
    },
    {
      "Name": "Company C",
      "Id": "249a4558-05c6-483f-9835-0056804791c9"
    }
  ],
  "DeleteIds": null,
  "LastSyncDateTime": "\/Date(1393580073773+0000)\/"
}

For the above json I would be mapping to a SyncWrapper where T is Company...

public class Company extends ModelBase {

    private String name;

    public Company(UUID id, String name) {
        super(id);
        setName(name);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Here's the issues:

Your field names in your Java classes don't match the field names in the JSON; capitalization matters. This is why you're getting back absolutely nothing after parsing.

I'm going to go with Gson examples simply because I know that off the top of my head. You can do the same things in Jackson, but I'd need to look them up:

public class SyncWrapper<T, I> {

    @SearializedName("LastSyncDateTime")
    private DateTime lastSyncDateTime;
    @SearializedName("Data")
    private Collection<T> data;
    @SearializedName("DeleteIds")
    private Collection<I> deleteIds;

This tells Gson which fields in Java map to the fields in JSON. You could also go with a field naming policy instead, since it looks like all your fields are upper camel case:

Gson g = new GsonBuilder()
             .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
             .build();

Now your fields will match up. The next issue is going to be that UUID class. That class in Java is not a string; it's a class that generates UUIDs. Just use String for the type that holds it in your Java class.

The DateTime class ... same issue. And on top of that you've got a bit of a weird value in your JSON for the date. You'll either want to store that as a String as well, or you're going to have to write a custom deserializer to deal with it.

With those changes, I think you're good to go.

Edit to add from the comments: If you really need the Java UUID class rather than just the String representation, you can write a chunk of code that takes care of this for you:

class UUIDDeserializer implements JsonDeserializer<UUID>
{
    @Override
    public UUID deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException
    {
        return UUID.fromString(je.getAsString());
    }
}

You can then register this with Gson:

Gson g = new GsonBuilder()
             .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
             .registerTypeAdapter(UUID.class, new UUIDDeserializer())
             .build();

This will populate the UUID typed fields in your class with UUID instances. This is the same thing you'd need to do with that funky date value.

I suggest using Jackson for this; it has a more clear API and does not require creating a new type as Gson (where you have to extend a class to be able to do that).

Example:

public static <T> T fromJsonToGenericPojo(
        String json, Class<?> classType, Class<?>... genericTypes) {

    JavaType javaType = TypeFactory.defaultInstance()
            .constructParametricType(classType, genericTypes);

    try {
        return OBJECT_MAPPER.readValue(json, javaType);
    } catch (IOException e) {
        LOGGER.error(e.getMessage(), e);
        throw new IllegalArgumentException(e);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM