Reputation: 2787
I have a json object that looks like this:
{
"user": {
"id": 1234
...
"photos": [
{
"url": "http://....."
...
},
{
"url": "http://....."
...
}
]
}
}
I want to write a custom deserializer for both user
and photos
.
so I have:
public class User {
private long id;
private ArrayList<Photo> photos;
...
public static class Deserializer implements JsonDeserializer<User> {
... // does the custom serialization of the User object
}
}
public class Photo {
private String url;
...
public static class Deserializer implements JsonDeserializer<Photos> {
... // does the custom serialization of the Photo object
}
}
and when initializing I do this:
new GsonBuilder()
.registerTypeAdapter(User.class, new User.Deserializer());
.registerTypeAdapter(Photos.class, new Photos.Deserializer());
However, when i deserialize the User
class, it hits the User
's deserializer but never hits the Photo
's deserializer. But if i get a json with the photo object not nested in the user json object like this:
{
"photos": [
{
"url": "http://....."
...
},
{
"url": "http://....."
...
},
{
"url": "http://....."
...
}
]
it will properly hit the Photo
's deserializer
Upvotes: 3
Views: 4152
Reputation: 21115
In short, there is a non-formal rule: once you declare a type adapter (or a (de)serializer that share the same concept in principle) for a certain type, then you have to manage its instantiation and its child fields yourself. Thus, when you deserialize the top-most User
, its id
and photos
are deserialized on your own. Note that the Photo.Deserializer
is invoked once you request for it explicitly like gson.fromJson(..., Photo.class)
or apply it implicitly (for the latter Gson uses built-in strategies by default, please see ReflectiveTypeAdapterFactory
for example) via deserialization contexts. The same principle goes to User
if you don't bind User.Deserializer
, therefore Gson uses ReflectiveTypeAdapterFactory.Adapter<T>
that just iterates through all fields itself using reflection. Even shorter: Gson does not merge multiple strategies (at least by default), so you either delegate object construction and setting up to Gson, or instantiate it completely.
Knowing that, User.Deserializer
can be implemented as the following:
final class User {
final long id;
final List<Photo> photos;
private User(final long id, final List<Photo> photos) {
this.id = id;
this.photos = photos;
}
static final class Deserializer
implements JsonDeserializer<User> {
private static final Type photoListType = new TypeToken<List<Photo>>() {
}.getType();
@Override
public User deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context) {
// Note that you must pick up properties first
final JsonObject jsonObject = jsonElement.getAsJsonObject();
return new User(
// And then delegate them to the deserialization context specifying the target type
context.deserialize(jsonObject.get("id"), long.class),
// You can deconstruct JsonElement recursively, but deserialization context respects Gson context built with GsonBuilder
// This also does trigger the Photo.Deserializer
context.deserialize(jsonObject.get("photos"), photoListType)
);
}
}
}
I'm assuming Photos
in your code is a typo and it's supposed to be Photo
. If it's not, then a similar solution might be implemented for Photos
.
final class Photo {
final String url;
private Photo(final String url) {
this.url = url;
}
static final class Deserializer
implements JsonDeserializer<Photo> {
@Override
public Photo deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context) {
final JsonObject jsonObject = jsonElement.getAsJsonObject();
return new Photo(
// jsonObject.get("url").getAsString() can be more simple, but it does not respect Gson instance configuration
context.deserialize(jsonObject.get("url"), String.class)
);
}
}
}
How it can be used:
final class Wrapper {
final User user;
private Wrapper(final User user) {
this.user = user;
}
}
final Gson gson = new GsonBuilder()
.registerTypeAdapter(User.class, new User.Deserializer())
.registerTypeAdapter(Photo.class, new Photo.Deserializer())
.create();
final Wrapper wrapper = gson.fromJson(JSON, Wrapper.class);
System.out.println(wrapper.user.id);
wrapper.user.photos.forEach(p -> System.out.println(p.url));
The output:
1234
http://.....
http://.....
Upvotes: 5
Reputation: 5589
I think the classes don't match the json file. In this json file:
{
"user": {
"id": 1234
...
"photos": {
"abc": {
"url": "http://....."
...
}
}
}
}
You have
public class User {
private int id;
private Photos photos;
}
public class Photos {
private MyUrl abc;
private MyUrl bcd;
private MyUrl cde;
...
}
public class MyUrl {
private String url;
}
To get an ArrayList of the photos the json should look like this (note the square brackets and its content):
{
"user": {
"id": 1234
...
"photos": [
{ "url": "http://....." },
{ "url": "http://....." },
...
{ "url": "http://....." }
]
}
}
}
This last jason responds to:
public class User {
private int id;
...
private ArrayList<Photo> photos;
}
public class Photo{
private String url;
}
Upvotes: 1