Spring JSON serialization, Gson deserialization - java

I'm currently having an issue with the deserialization of certain inner-objects, in spring, I initialize all of my objects before outputting them using #ResponseBody.
As an example, this is a response:
[{id:1, location:{id:1, ... extra location data}},
{id:2, location:1}
]
Now, GSON throws an error as it is not able to understand that location:1 refers to the location object already deserialized in the previous object.
Deserialization is done in the following method:
#Override
public void handleReader(Reader reader) {
try {
String json = readerToString(reader);
T object = getGson().fromJson(json, returnType);
handleObject(object);
} catch (Exception e) {
Sentry.captureException(e);
}
}
As an example, this is called through a regular generic class, I'd use the type Event[] as the T generic in order to return an array.
How can I either fix this using Gson or make spring output the full data every time? Ideally I'd like to fix with Gson as it would allow for seriously reduced bandwidth but I'm not too fussed at this point.
My Spring returning method is as follows:
#Override
public List<T> list() {
return service.findAll();
}
with the initialization like so:
#Override
#Transactional
public List<Event> findAll() {
List<Event> list = eventRepository.findByArchivedFalse();
for (Event event : list) {
this.initialize(event);
}
return list;
}
#Override
public Event initialize(Event obj) {
Hibernate.initialize(obj.getLocation());
Hibernate.initialize(obj.getLocation().get... inner data here);
return obj;
}
I imagine this is going to require a real structure review but, if I can help it, I'd like to keep the structure roughly the same.

You're going to have to write a custom deserializer, if you're not willing to change the JSon. However, changing the JSon is exactly what I would recommend.
Option 1: Changing the JSon
I think the right thing to do is to have two separate messages, e.g.
{
"uniqueLocations":
[
{"id":1, ... extra location details} ,
],
"locationMap":
[
{"id":1,"location":1},
{"id":2,"location":1}
... etc.
]
}
This is clearer; this separates your json so that you always have the same types of data in the same places.
Option 2: Making Gson able to do more complicated deserializations
However, if you're not willing to do that, you could write a custom deserializer. The most straightforward way to do that, extending TypeAdapter, only uses specific, concrete classes, not parameterized types. However, if you want to use a parameterized type, you must use a TypeAdapterFactory.
You can read more about how to do this here: How do I implement TypeAdapterFactory in Gson?

Related

Modify JSON AST whilst deserializing without annotations

I'm trying to write some code that will deserialize JSON into somebody elses class. That is, I don't own the target class so I can't annotate it.
In particular, this class has some helper methods that are complicating the deserialization process. Something like this:
class Result {
private List<String> ids;
public List<String> getIds() {
return ids;
}
public void setIds(List<String> ids) {
this.ids = ids;
}
// Helpers
public String getId() {
return this.ids.isEmpty() ? null : this.ids.get(0);
}
public void setId(String id) {
this.ids = List.of(id);
}
}
When serialized, we get both ids and id come through as fields:
{
"ids": ["1", "2", "3"],
"id": "1"
}
And then when deserializing this JSON, Jackson is calling both setters - reasonably enough - and thus the object is wrong. The end result is that the ids field is set to ["1"] and not ["1", "2", "3"] as it should be.
So what I want to be able to do is fix this. And I'm thinking that the easiest/safest/best way is to be able to modify the JSON AST somewhere in the deserializing process. Specifically by just removing the "id" field from the JSON so that it doesn't get seen by the standard deserializer. (I know this works by doing it manually with string manipulation, but that's awful)
I could write a full deserializer for all the fields, but then I'm beholden to maintaining it if any new fields are added in the future, when all I actually want to do is ignore one single field and have everything else processed as normal. The real class actually has about a dozen fields at present, and I can't guarantee that it won't change in the future.
What I can't work out is how to do this. I was really hoping there was just some standard JsonDeserializer subclass that would let me do this, but I can't find one. The best I've been able to work out is a normal StdDeserializer that then uses parser.getCodec().treeToValue() - courtesty of this answer - except that this results in an infinite loop as it calls back into the exact same deserializer every time!
Frustratingly, most answers to this problem are "Just annotate the class" - and that's not an option here!
Is there a standard way to achieve this?
Cheers
There are the Jacksons mixins for exactly this case, a super useful feature!
For your case, define the mixin class and annotate it as if you were annotating the original; you only need to include the overrides, e.g.:
#JsonIgnoreProperties({ "id" })
public class ResultMixin {
// nothing else required!
}
Now, you will have to hook on the ObjectMapper creation to define the mixin. This depends on the framework you are using, but in the end it should look like this:
ObjectMapper om = ...
om.addMixIn(Result.class, ResultMixin.class);
Now this ObjectMapper will take into account the information from you mixin to serialize objects of type Result (and ignore the synthetic id property).
And, of course, I've just found out how to do it :)
I can use BeanDeserializerModifier instead to make things act as needed.
public class MyDeserializerModifier extends BeanDeserializerModifier {
#Override
public List<BeanPropertyDefinition> updateProperties(final DeserializationConfig config,
final BeanDescription beanDesc, final List<BeanPropertyDefinition> propDefs) {
if (beanDesc.getBeanClass().equals(Result.class)) {
propDefs.removeIf(prop -> prop.getName().equals("id"));
}
return super.updateProperties(config, beanDesc, propDefs);
}
#Override
public BeanDeserializerBuilder updateBuilder(final DeserializationConfig config, final BeanDescription beanDesc,
final BeanDeserializerBuilder builder) {
if (beanDesc.getBeanClass().equals(Result.class)) {
builder.addIgnorable("id");
}
return super.updateBuilder(config, beanDesc, builder);
}
}

Java Gson Deserialization of selected fields from a list inside an object

I'm trying to figure out a way to selectively de-serialize specific fields from flickr.
{"photos":{"page":1,"pages":10,"perpage":100,"total":1000,"photo":[{"id":"","owner":"","secret":"","server":"","farm":,"title":"","ispublic":,"isfriend":,"isfamily":0,"url_s":"","height_s":"","width_s":""},...]}
I receive an object that contains two lists (photos and photo) and i would like to model in Java only the id, url_s and title from photo.
I figured out that I can create my java module with #expose annotation for the fields i'm interested in and than use
builder.excludeFieldsWithoutExposeAnnotation();
this way I'll control which fields get deserialized from photo (still not sure it's gonne work that way), but what about photos?
My questions are:
Is there a way to ignore that list? do I have to model a Java class that contains two lists (with their respective fields) just to grab what I need from the second list?
expanding upon the former question, if my module class for photo is:
public class GalleryItem {
#Expose()
private String mCaption;
#Expose()
private String mId;
#Expose()
private String mUrl;
}
can i call gson only on the part i need?
Type galleryListType = new TypeToken<ArrayList<GalleryItems>> (){}.getType();
List<GalleryItems> itemsList = gson.fromJson(jsonString, galleryListType);
can I somehow use setExclusionStrategies to skip the Photos list?
gsonBuilder.setExclusionStrategies(new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes f) {
**return f.getName().contains("photos")**;
}
#Override
public boolean shouldSkipClass(Class<?> incomingClass) {
return ;
}
});
I've already implemented a solution using raw JSONObject/JSONArray but I'm curious regarding using GSON for the task at hand.
Thanks in advance!
Gson provides deserialisation on Java fields. If you mark as a transient your variable, Gson will not serialised to JSON. Gson also provides finer deserialisation control via annotations: #Expose(deserialize = false/true)
The last option would be writing your custom JsonDeserializer<T>

Custom object deserializes fine in Jax-RS but if it is used in another object it doesn't work

Deserializing works fine if I just pass my custom object through
#POST
public Response saveCustomObject(CustomObject data)
{
// Prints correct value
System.out.println(data);
}
However, if it is a property on another object, it just gets the default value of my custom object
#POST
public Response saveCustomObjectWrapper(CustomObjectWrapper data)
{
// Prints incorrect value
System.out.println(data.getCustomObject());
}
My provider is registered and looks like this:
public CustomObject readFrom(Class<CustomObject> type, Type type1, Annotation[] antns, MediaType mt, MultivaluedMap<String, String> mm, InputStream in) throws IOException, WebApplicationException
{
try {
return new CustomObject(IOUtils.toString(in));
} catch (Exception ex) {
throw new ProcessingException("Error deserializing a CustomObject.", ex);
}
}
The problem is that the reader for all other objects doesn't do lookup/delegation while unmarshalling. What I mean by that, can be seen in this answer, where one reader looks up another reader based on the type. Assuming the format is JSON, whether you're using MOXy (the default with Glassfish) or Jackson, the result is the same. The reader is smart enough to handle the the JSON by itself, so doesn't need to lookup any other readers.
One solution would be to create another reader for the wrapper class, and do lookup/delegation, as seen in the link above. If you have a lot of these situations, you may can extend the default reader, and override its unmarshalling method, but I would completely advise against this, unless you really know what you're doing.
Another solution, depending on the serializer you're using, is to write JsonDeserializer (for Jackson) or XmlAdapter (for MOXy or Jackson). For Jackson an example would be something like (you can see a better example here)
public class CustomObjectDeserializer extends JsonDeserializer<CustomObject> {
#Override
public CustomObject deserialize(JsonParser jp, DeserializationContext dc)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
return new CustomObject("Hello World");
}
}
#JsonDeserialize(using = CustomObjectDeserializer.class)
public class CustomObject {
public String message;
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public CustomObject(String message) { this.message = message; }
public CustomObject(){}
}
In which case, there is no need for a custom reader at all. This will handle CustomObjects and objects that have CustomObject as a member. One problem with this is I'm not sure how or if you can get the InputStream. You just need to use the Jackson APIs to parse the JSON.
If you want to use Jackson instead of the default MOXy for glassfish, you can just add the Jackson dependency
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.13</version>
</dependency>
Then register the JacksonFeature, or simply disable MOXy, as mentioned here. If you want to continue using MOXy, I don't know if there is such thing as a class level adapter, so you will still need the reader as well as create a XmlAdapter for class members. It's a bit of a hassle, but that's why I recommend Jackson, for many other reasons, besides this particular use case. You can see an example of an adapter here
Now a lot of this answer is based on the assumption you are using JSON format, as you haven't specified the media type you are using. If it some other format, then I think maybe your only solution is to create another customer reader for the wrapper.

Populate POJO with array (root JSON node) using Jackson

I'm using Jackson and RESTEasy to hook into an external API. The API mainly returns simple objects which I have managed to successfully populate into POJOs.
I'm hitting a problem where I get an array of objects back e.g.
[
{
"variable1": "someValue1",
"variable2": "someValue2",
"variable3": "someValue3"
}
{
"variable1": "someValue4",
"variable2": "someValue5",
"variable3": "someValue6"
}
{
"variable1": "someValue7",
"variable2": "someValue8",
"variable3": "someValue9"
}
]
I have 2 classes: one called VariableObject which looks like this:
public class VariableObject {
private String variable1;
private String variable2;
private String variable3;
}
and VariableResponse which looks like:
public class VariableResponse {
private List<VariableObject> variableObjects;
}
My client uses JAXRS Response class to read the entity into the class i.e
return response.readEntity(VariableResponse.class);
I get a stack trace which reads:
Caused by: org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of VariableResponse out of START_ARRAY token
I understand you can return these as a List of POJOs i.e List quite easily, but this is not what I want to do.
The question really is two parts:
a. Can I possibly populate the VariableResponse POJO using Jackson (some how) preferably without a customer deserialiser? Maybe some annotation exists (this would be ideal)?
b. Is there some way to detect if an Array is being retuned as the root JSON node in the response and then act accordingly?
Help greatly appreciated.
Your JSON is indeed an array of objects.
You can deserialize it with:
response.readEntity(new GenericType<List<VariableObject>>() {});
And then create a new instance of VariableResponse passing resulting List as a constructor parameter like this:
public class VariableResponse {
private final List<VariableObject> variableObjects;
public VariableResponse(List<VariableObject> variableObjects) {
this.variableObject = new ArrayList<>(variableObjects);
}
}
You might forget to add comma after each {..}. After correcting your JSON string, I converted it into ArrayList<VariableObject> using TypeReference and ObjectMapper.
sample code:
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
...
TypeReference<ArrayList<VariableObject>> typeRef = new TypeReference<ArrayList<VariableObject>>() {};
ObjectMapper mapper = new ObjectMapper();
try {
ArrayList<VariableObject> data = mapper.readValue(jsonString, typeRef);
for (VariableObject var: data) {
System.out.println(var.getVariable1()+","+var.getVariable2()+","+var.getVariable3());
}
} catch (Exception e) {
System.out.println("There might be some issue with the JSON string");
}
output:
someValue1,someValue2,someValue3
someValue4,someValue5,someValue6
someValue7,someValue8,someValue9
If you prefer your own response type direct.
Try just extending ArrayList?
public VariableResponse extends ArrayList<VariableObject> {
}

GSON: Deserialize inner Object based on String in containing object

I'm trying to create a custom deserializer in Gson for an object that is really just a wrapper for a bunch of disparate types with a type identifier.
Here's a simplified overview of my problem domain:
I have users sending messages to each other that can contain a variety of unrelated domain objects, and I want to deserialize it to something like:
public class Message {
public String messageType;
public Object messageData;
}
The messageData object is constructed via JavaScript the programmers decided to just jam every object type into one field "messageData". messageData can be any number of domain objects like: User, Video, Website, Picture, which do not share a base class or interface.
So the (simplified) json object could look like:
{ "messageType": "video", "messageData": { "videoId": 1, "videoTitle": "my vid" } }
or
{ "messageType": "picture", "messageData": { "pictureId": 1, "pictureUrl": "http://www.example.com/cat.jpg" } }
The goal would be to take the messageType and use that to choose a proper class to deserialize it into.
I have come up with something like this:
public class MessageJsonDeserializer implements JsonDeserializer<Message> {
#Override
public Message deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
JsonObject obj = json.getAsJsonObject();
Message message = new Message();
message.messageType = obj.get("messageType").getAsString();
message.messageData = MessageDataMapper.map(message.messageType, obj.get("messageData")); // maps and casts to the correct Video/Picture,Website etc..
return message;
}
}
This seems to work ok, but let's say that Message has a LOT of other fields that could be automatically serialized, then I'd have to manually parse the JsonObject and extract those fields.
Is there a way that I can use a hybrid approach and have Gson automatically serialize the non-Object fields, but use a custom serializer for that messageData field that ALSO takes into account the messageType?
It might be a bit late, but this answer regarding the RuntimeTypeAdapter might help you in solving this:
https://stackoverflow.com/a/15737704/433421
The RuntimeTypeAdapter enables you to evaluate a self-configured type-attribute in the JSON, in your case "messageType", and register its values with provided POJO-classes.
Look at the link provided in this answer to see an usage-example in the javadoc.
GSon would handle serialization and deserialization of those POJO-classes itself, as I understand it.
If those fields in messageData contain arbitrary objects, you would have to register custom JsonDeserializer-instances as you did with MessageJsonDeserializer.
Hope this helps.
Sadly, no. At least not that I was ever able to determine. There is no way to have it do something like "custom deserialize field X, but super.deserialize() for the rest of it". God knows I tried. If it's any consolation, that seems to be true with every Json deserializer I've looked at.

Categories