All classes in my model are subclasses of a common base-class Node.
When serializing/deserializing with Jackson, I want references to other objects to be replaced by their ID. The problem is, that the ID is a combination of two values: the ID of the instance itself, and the ID of a source. I do this with a custom serializer and deserializer.
Serializing is no problem. I write JSON with a structure like this:
{"id":1,"source":2,"name":"Some record","reference":3}
But when deserilizing, I need to know the ID of the source and the ID of the referenced node, to be able to look it up in my custom deserializer.
Is it possible, to access the values of the deserialized instance, to get access to the ID of the source when deserializing the reference?
Here is what I tried so far:
public class MyDeserializer extends JsonDeserializer<Node>
{
#Override
public NodeData deserialize(...)
{
Node parent = (Node)parser.getCurrentValue();
Long id = parent.getId();
Long id = parser.getLongValue();
return NodeDataService.INSTANCE.get(source, id);
}
}
But parser.getCurrentValue() always returns a null.
My best solution so far is, to write a cooperation pair of deserializers.
The first one is annotated to the getter of the attribute source and stores
the value as per-call attribute. The second looks like this:
public class MyDeserializer extends JsonDeserializer<Node>
{
#Override
public NodeData deserialize(...)
{
Long source (Long)context.getAttribute("SOURCE");
Long id = parser.getLongValue();
return NodeDataService.INSTANCE.get(source, id);
}
}
This works, but I am asking myself, if there is an easier way to achieve this.
This question looks like it is possible, like I did it in my first attempt - but only while serializing:
Jackson How to retrieve parent bean in a custom Serializer/Deserializer
getCurrentValue() will return null as you've entered into a new JSON object but not yet set the current value. You need to look at the stack of deserialized values in the parser context.
I answered something similar here, which is the deserialization equivalent of the serialization question you linked to: Jackson JSON gives exception on collection of nested class
In summary you can get the stream context:
JsonStreamContext ourContext = p.getParsingContext();
and then repeatedly call getParent() on contexts to walk up the chain, calling getCurrentValue(). The value is set into the stream context as soon as the standard bean deserializer constructs the object.
Related
I've searched and found Jackson ObjectMapper throwing NullPointerException even with NON_NULL, but I don't have control of the class to change my setter.
I have am being given
{... "fieldNames": null,...}
and am supposed to deserialize it to
Collection<String> fieldNames
I don't have control of the class or the json I'm getting.
Is there some setting I can use to handle for this? I've looked at DeserializationFeature, but could not find it
You can use mix-ins when you don't control the class you are deserializing. You don't mention the name of the class containing Collection<String> fieldNames so lets assume it's called Fields. Then create a new class:
class FieldsMixin {
#JsonSetter(nulls = Nulls.SKIP)
Collection<String> fieldNames;
}
and add the mixin class to your ObjectMapper associating it with the original unmodified class:
mapper.addMixIn(Fields.class, FieldsMixin.class);
This is a new feature in Jackson 2.9 and as you guess it will skip calling a setter method or otherwise set a field if the value in JSON is null. Documentation
I have developed this webapp using Spring MVC + Hibernate.
I retrieve all my objects in a Service then return them directly to the controller. These objects generally are lazily initialized so collections are empty.
So for object User:
User
{
int idUser;
City city;
String name;
List<User> friends;
}
I return an object with just idUser and name, City and Friends are not initialized.
I want to take advantage of all my services methods (without modifying them) to provide a REST api, so if from my ApiController I request to get user with id 1, I retrieve all useful information about this user in JSON.
I tried using GSON but as soon as it tries to jsonize the city object it crashes because it has been lazily initialized. Same goes for the friends collection.
For collections it's not much of a big deal since in my api I would have another request url where you can get all friends given a user Id, but in the case of relationships with a single object (like the city in this example), I would like to return the id of the City which by definition of Lazy loading is indeed set.
How can I tell GSON to jsonize just the cityId attribute of City instead of the whole object?
Will nulling the rest of collections be a good solution so they're not converted into JSON? Is there any other way to explicitly tell GSON to ignore these attributes?
I believe you need to put your gson.toJson(...) within a transaction, in springMVC typically #Transactional at controller method where you are doing the actual serialization will do.
If you really want to skip fields or selectively serialize fields of using Gson, you can check https://stackoverflow.com/a/3341439 for gson exclusion strategy. You can skip based on Field Annotation or field name or the entire referenced class.
Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
public boolean shouldSkipClass(Class<?> clazz) {
return <class exclusion logic, return true for exclusion else false>;
}
public boolean shouldSkipField(FieldAttributes f) {
return <field exclusion logic>;
}
}).create();
I need to add new property to an object, when serializing to JSON. The value for the property is calculated on runtime and does not exist in the object. Also the same object can be used for creation of different JSON with different set ot fields (kind of having a base class with subclasses, but I don't want to create ones just for JSON generation).
What is the best way of doing that, which doesn't involve creation of custom serializer class, which will take care of serializing of whole set of object's fields? Or may be it is possible to inherit some "basic" serializer, and simply take it's output and add new field to it somehow?
I learned about mixins, and looks like it is possible to rename/hide some fields, however it seems not be possible to add an extra one.
Can you not just add a method in value class? Note that it does not have to be either public, or use getter naming convention; you could do something like:
public class MyStuff {
// ... the usual fields, getters and/or setters
#JsonProperty("sum") // or whatever name you need in JSON
private int calculateSumForJSON() {
return 42; // calculate somehow
}
}
Otherwise you could convert POJO into JSON Tree value:
JsonNode tree = mapper.valueToTree(value);
and then modify it by adding properties etc.
2021 calling...
Simplest way I found to do this is #JsonUnwrapped:
public class Envelope<T> {
#JsonUnwrapped // content's fields are promoted alongside the envelope's
public T content;
// Transmission specific fields
public String url;
public long timestamp;
}
This works (bi-directionally) so long as Envelope's fieldnames do not clash with those of content. Also has a nice feature of keeping the transmission properties at the end of the serialised JSON.
One option is to add a field for this property and set it on the object before writing to JSON. A second option, if the property can be computed from other object properties you could just add a getter for it, for example:
public String getFullName() {
return getFirstName() + " " + getLastName();
}
And even though there's no matching field Jackson will automatically call this getter while writing the JSON and it will appear as fullName in the JSON output. If that won't work a third option is to convert the object to a map and then manipulate it however you need:
ObjectMapper mapper //.....
MyObject o //.....
long specialValue //.....
Map<String, Object> map = mapper.convertValue(o, new TypeReference<Map<String, Object>>() { });
map.put("specialValue", specialValue);
You're question didn't mention unmarshalling but if you need to do that as well then the first option would work fine but the second two would need some tweaking.
And as for writing different fields of the same object it sounds like a job for #JsonView
I'm having issues using Jackson to map a Javascript posted JSON array of hashes (Tag).
Here is the data received by the controller #RequestBody (It is send with correct json requestheader):
[{name=tag1}, {name=tag2}, {name=tag3}]
Here is the controller:
#RequestMapping(value = "purchases/{purchaseId}/tags", method = RequestMethod.POST, params = "manyTags")
#ResponseStatus(HttpStatus.CREATED)
public void createAll(#PathVariable("purchaseId") final Long purchaseId, #RequestBody final List<Tag> entities)
{
Purchase purchase = purchaseService.getById(purchaseId);
Set<Tag> tags = purchase.getTags();
purchaseService.updatePurchase(purchase);
}
When I debug and view the 'entities' value it shows as an ArrayList of generic objects, not as a list of objects of type 'Tag' as I would expect.
How can I get jackson to map a passed array of objects to a list of obejcts of type 'Tag'?
Thanks
It sounds like Spring is not passing full type information for some reason, but rather a type-erased version, as if declaration was something like List<?> tag. I don't know what can be done to fully resolve this (may need something from Spring integration team), but one work-around is to define your own type like:
static class TagList extends ArrayList<Tag> { }
and use that instead. This will retain generic parameterization through super-type declarations so that even if Spring only passes equivalent of TagList.class, Jackson can figure out the Tag parameter.
Another way to do this is to rather obtain an array than a List, as follows:
#RequestBody Tag[] entities
Jackson requires a default constructor with no parameters on custom Objects, so you'll need to simply add a default constructor to your Tag class.
In your case simply add to your Tag class:
public Tag(){}
I'm aware that there are multiple way to tell JacksonJson to ignore
properties during rendering but all of them are static. (JasonIgnore, MixIn classes, ..).
This is my scenario. A domain object can implement a interface called FilteredDomain to
allow it to be dynamically filtered. The interface is simple and only exposes one method
"getIgnoreProperties". (A list of properties to ignore).
I then register a Custom Serializer that binds to the FilteredDomain object. The
code looks something like:
private class FilteredDomainSerializer extends JsonSerializer<FilteredDomain> {
public void serialize(FilteredDomain arg, JsonGenerator jgen,
SerializerProvider provder) throws IOException,
JsonProcessingException {
final BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(arg);
for (PropertyDescriptor pd : wrapper.getPropertyDescriptors()) {
final String name = pd.getName();
if (arg.getIgnoreProperties().containsKey(name))
continue;
final Object value = wrapper.getPropertyValue(name);
jgen.writeObjectField(name, value);
}
}
}
First, I really dislike that I need to use the Spring Bean wrapper to get a list of all properties and iterate through them (There must be a way to do this is jackson json).
Second, The code still dosen't work. I get the error:
org.codehaus.jackson.JsonGenerationException: Can not write a field name, expecting a value
at org.codehaus.jackson.impl.JsonGeneratorBase._reportError(JsonGeneratorBase.java:480)
at org.codehaus.jackson.impl.Utf8Generator.writeFieldName(Utf8Generator.java:270)
at org.codehaus.jackson.JsonGenerator.writeObjectField(JsonGenerator.java:1088)
at com.rootmusic.util.SystemJsonObjectMapper$ValueObjectSerializer.serialize(SystemJsonObjectMapper.java:65)
at com.rootmusic.util.SystemJsonObjectMapper$ValueObjectSerializer.serialize(SystemJsonObjectMapper.java:1)
at org.codehaus.jackson.map.ser.ContainerSerializers$IndexedListSerializer.serializeContents(ContainerSerializers.java:304)
at org.codehaus.jackson.map.ser.ContainerSerializers$IndexedListSerializer.serializeContents(ContainerSerializers.java:254)
at org.codehaus.jackson.map.ser.ContainerSerializers$AsArraySerializer.serialize(ContainerSerializers.java:142)
at org.codehaus.jackson.map.ser.MapSerializer.serializeFields(MapSerializer.java:287)
at org.codehaus.jackson.map.ser.MapSerializer.serialize(MapSerializer.java:212)
at org.codehaus.jackson.map.ser.MapSerializer.serialize(MapSerializer.java:23)
at org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:606)
at org.codehaus.jackson.map.ser.StdSerializerProvider.serializeValue(StdSerializerProvider.java:280)
The error comes from the fact that you are not writing START_OBJECT / END_OBJECT around field-name/value pairs, so that should be easy to fix.
As to more dynamic filtering, you could read this blog entry which includes standard methods. #JsonView works if you have sets of static definitions (one of which you can dynamically select on per-serialization basis), but if you want yet more dynamic system, #JsonFilter is the way to go.
Alternatively, another relatively simple way would be to first "convert" your POJO into a Map:
Map props = objectMapper.convertValue(pojo, Map.class);
(which is similar to serializing it as JSON, except that result is a Map which would render as JSON)
and then selectively trim Map, and serialize that as JSON. Or, if you prefer, you can use JsonNode ("tree model") as the intermediate thing to modify and then serialize.