Serialize one class in two different ways with Jackson - java

In one of our projects we use a java webapp talking to a MongoDB instance. In the database, we use DBRefs to keep track of some object relations. We (de)serialize with POJO objects using jackson (using mongodb-jackson-mapper).
However, we use the same POJOs to then (de)serialize to the outside world, where our front end deals with presenting the JSON.
Now, we need a way for the serialization for the outside world to contain the referenced object from a DBRef (so that the UI can present the full object), while we obviously want to have the DBRef written to the database, and not the whole object.
Right now I wrote some untested static nested class code:
public static class FooReference {
public DBRef<Foo> foo;
// FIXME how to ensure that this doesn't go into the database?
public Foo getFoo() {
return foo.fetch();
}
}
Ideally I would like a way to annotate this so that I could (de)serialize it either with or without the getFoo() result, probably depending on some configuration object. Is this possible? Do you see a better way of going about doing this?

From looking at options, it seems you can annotate properties to only be shown if a given View is passed to the ObjectMapper used for serialization. You could thus edit the class:
public static class FooReference {
public DBRef<Foo> foo;
#JsonView(Views.WebView.class)
public Foo getFoo() {
return foo.fetch();
}
}
and provide:
class Views {
static class WebView { }
}
and then serialize after creating a configuration with the correct view:
SerializationConfig conf = objectMapper.getSerializationConfig().withView(Views.WebView.class);
objectMapper.setSerializationConfig(conf);
Which would then serialize it. Not specifying the view when serializing with the MongoDB wrapper would mean the method would be ignored. Properties without a JsonView annotation are serialized by default, a behaviour you can change by specifying:
objectMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
More info is available on the Jackson Wiki.
There are still other alternatives, too, it turns out: there are Jackson MixIns which would let you override (de)serialization behaviour of parts of a class without modifying the class itself, and as of Jackson 2.0 (very recent release) there are filters, too.

Use a custom JSONSerializer and apply your logic in the serialize method:
public static class FooReference {
public DBRef<Foo> foo;
#JsonSerialize(using = CustomSerializer.class)
public Foo getFoo() {
return foo.fetch();
}
}
public class CustomSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
// jgen.writeObjectField ...
}
}

Related

Jackson: passing exta objects during deserialization

During deserialization, how can I pass in an extra object that's needed to initialize some class member? If I were doing deserialization "manually," the implementation might look like:
public class MyClass {
private MyDocumentObject do;
private String food;
public MyClass(JsonNode node, MyDocument document) {
this.do = document.createMyDocumentObject();
this.food = node.get("food").asText();
}
public String getFood() {
return this.food;
}
}
But I'd like to use Jackson's automatic mapping facilities and use a decorated constructor or custom deserializer, etc. and avoid implementing the deserialization within the class itself. Looking at example implementations using #JsonCreator or extending StdDeserializer, I can't see a way of saying "hey, please use this MyDocument object when you call the constructor." I'd like to avoid implementing and exposing a separate method that accepts a MyDocument that I have to invoke on every object that gets deserialized, e.g.
public createDocumentObject(MyDocument document) {
this.do = document.createMyDocumentObject();
}
I don't want to have this method at all, but if I had to, I'd want Jackson to call this method for me right after deserialization. That means I'd still have to somehow tell Jackson which MyDocument to use.

Spring, JSON serializer, serialize a field only in some cases

I have a class Order:
#Data
#Entity
public class Order {
private List<Project> projects;
// more fields
}
I have a two API methods in my controller:
#GetMapping
public ResponseEntity<List<Order>> getOrders() {
return ResponseEntity.ok(orderService.getOrders());
}
#GetMapping("/{id}")
public ResponseEntity<Order> getOrder(#PathVariable long id) {
return ResponseEntity.ok(orderService.getOrder(id));
}
So in this case projects is always sent via JSON, if its present its just getting serialized, if its not present its getting fetched lazily and then serialized. I could avoid it being serialized by annotating the field with #JsonIgnore. But the problem is that i want to send it sometimes and sometimes i dont. For example in getOrders() i dont want the projects to be serialized. In getOrder(...) i would want projects to be serialized. Is there any way to tell during runtime either inside custom code or by an annotation that i want to send it in one specific case and not in another case? The only thing i figured out is that - shortly before serializing - i can initialize projects with null and annotate the entity with #JsonInclude(JsonInclude.Include.NON_NULL). That way it wouldnt be sent and if i want to send it i can just avoid initializing it with null. But obviously i dont want to iterate over each Order in O(n) just to initialize its projects with null.
This is easy to achieve using "JSON Views".
First, define some classes to represent each view (e.g. internal/external):
public class OrderViews {
public static class OnlySomeFields {}
public static class AllFields extends OnlySomeFields {}
}
Next, on your class, assign a view to each field:
public class Order {
#JsonView(OrderViews.OnlySomeFields.class)
private String foo;
#JsonView(OrderViews.AllFields.class)
private String bar;
// getters/setters/etc
}
Then, in your controller, you can specify which view to use for each method:
#RestController
public class MyController {
#JsonView(OrderViews.AllFields.class)
#GetMapping("/with-all-fields")
public Order getOrderAllFields() {
return orderService.getOrder();
}
#JsonView(OrderViews.OnlySomeFields.class)
#GetMapping("/with-some-fields")
public Order getOrderAllFields() {
return orderService.getOrder();
}
}
With this setup, navigating to /with-all-fields returns a JSON containing foo and bar, while navigating to /with-some-fields returns a JSON only containing foo.
You can use this technique to selectively serialize specific fields, and should be able to apply it to your use case.

how to prevent a return value from cache to be changed in spring cacheable

i'm working around spring 3.1 annotation cache with ehcache as a cache implement.
a method with return value like this
#Cacheable("cache")
public MyObject getObj(Object param);
i got a myobject return value for the first time,and it's editable.
ehcache can do something for that by setting "copyOnRead" or "copyOnWrite".
it will force serialize object on read/write.
but at the first time spring will not get value from cache,it always return by method itself.
is there some way to get a readonly return value?
You could write your own aspect that always creates a copy of the returned value, which would make you independent of some Ehcache settings.
At first, a marker annotation like #CopyReturnValue would be nice for expressing the pointcut:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface CopyReturnValue {
}
Now, the aspect can use this annotation for the pointcut expression:
#Aspect
#Component
public class CopyReturnValueAspect {
#Around("#annotation(CopyReturnValue)")
public Object doCopyReturnValue(ProceedingJoinPoint pjp) throws Throwable {
Object retVal = pjp.proceed();
Object copy = BeanUtils.cloneBean(retVal); // create a copy in some way
return copy;
}
}
Finally, add the annotation to your method:
#CopyReturnValue
#Cacheable("cache")
public MyObject getObj(Object param);
For the CopyReturnValueAspect I use BeanUtils to create a copy of the returned value - just as an example. For further information on that topic, you might want to look at How to copy properties from one Java bean to another?
Oh, don't forget to enable #AspectJ support in you Spring configuration if you haven't already:
<aop:aspectj-autoproxy />
I had the same problem with the spring cache. I didn't want to receive the same java objects from the cache.
In my case i want to cache big java objects with many fields and so on. So it is very painful to copy all the data classes with deep copy. I read the article about copy java objects with serialization.
http://www.javaworld.com/article/2077578/learn-java/java-tip-76--an-alternative-to-the-deep-copy-technique.html
This brought me to the idea to cache only the serialized data. Every time a object is read from the cache it is deserialized.
For the serialization i used apache commons helper methods
#Override
public SerializedQuestions readUserQuestion(UID questionId, Locale locale) {
byte[] serializedData = readCachedUserQuestion(questionId, locale);
Object deserializedobject = org.apache.commons.lang.SerializationUtils.deserialize(serializedData);
return (SerializedQuestions) deserialize;
}
#Override
#Cacheable(value = SpringCacheKeys.USER_QUESTION_CACHE)
public byte[] readCachedUserQuestion(UID questionId, Locale locale) {
//read object from db
SerializedQuestions questions = new SerializedQuestions()
return org.apache.commons.lang.SerializationUtils.serialize(questions);
}
It depends on the spring configuration if the call to readCachedUserQuestion could be in the same class or not. Per default only extern calls to a method are cached.
I've found a little dirty solution that worked for me. by creating another class that it contains the same proprieties of The returned Object and then I've mapped the returned Object to my new Object using ModelMapper.
for example i have a class MyObject:
public class MyObject {
private Long id;
private Long label;
//getters and setters
}
the new Created class:
public class MyObjectCopy {
private Long id;
private Long label;
//getters and setters
}
and a cachable method that returns MyObject:
#Cacheable("cache")
public MyObject getMyObject();
and to prevent cache to be modified : i must map that object to my classCopy then i work on it:
MyObjectCopy myObjectCopy = modelMapper.map(myobject, MyObjectCopy.class);
dont forget to create a copy class for the nested objects;

Jackson Ser/Deser: Proxying an object to/from an id w/ a different key

Apologies in advance. This seems like a simple task, but hours later on Google and with guess/check, I still can't figure it out.
I'm writing a Java convenience wrapper library for an API my company provides. One of the classes looks something like this:
class View extends Model<View>
{
List<Column> columns;
Column primaryColumn;
}
However, our API actually wants a primaryColumnId integer, not an actual Column object. I want to maintain the strongly-typed getPrimaryColumn() and setPrimaryColumn(Column) in the library to reduce developer error, but I'm having significant difficulty writing some sort of translation between the getter/setter that we need to ser/deser to/from JSON.
I'm using the standard Bean serialization strategy. I'd like to avoid the wholly-custom approach because in reality View has dozens of fields. Here's what I've figured out so far.
I think (haven't tested yet) that I can handle the serialization case simply by creating a custom JsonSerializer that looks something like:
public static class ColumnIdSerializer extends JsonSerializer<Column>
{
#Override
public void serialize(Column column, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeFieldName("primaryColumnId");
jsonGenerator.writeNumber(column.id);
}
}
And then assigning the annotation to the appropriate place:
#JsonSerialize(using = Column.ColumnIdSerializer.class)
public Column getPrimaryColumn() { /* ... */ }
This allows me to serialize the id rather than the whole class, and rename the key from primaryColumn to primaryColumnId.
Now, we get to deserialization. Here I run into three problems.
The first is that in order to successfully deserialize the column from the id, we have to first have the list of columns. This is solvable using #JsonPropertyOrder on the class. Great, that's done.
The second is that I need to tell Jackson to look under primaryColumnId rather than primaryColumn for the value. I don't know how to do this; the JsonDeserializer appears to kick in after the key has already been found, so it's too late to modify it. JsonSchema looks like it might be relevant but I can't find any documentation or internet chatter on how to use it.
The third is that from the custom JsonDeserializer class I'll have to be able to reference the View that's being deserialized in order to ask it for a Column in return for my id int. There doesn't appear to be a way to do that.
Should I just cave and add a public getPrimaryColumnId() and setPrimaryColumnId(Integer), or is there a way to overcome these obstacles?
So I'd propose something like this:
class CustomView
{
private final View parent;
public CustomView(View view){
parent = view;
}
// Jackson needs a no-arg constructor
public CustomView(){
parent = new View();
}
// ...
public List<Columns> getColumns(){ ... }
public void setColumns(List<Columns> columns){ ... }
public int getPrimaryColumn(){
return parent.getPrimaryColumn().getColumnId();
}
public void setPrimaryColumn(int column){
parent.getPrimaryColumn().setColumnId(column);
}
//...
// don't use `get` in the method name here to avoid serialization
public View rawView(){
return parent;
}
}
If needed this can be written to extend View, but be careful to mask methods where appropriate.
Turns out that since Jackson does nasty reflection, it can see through private methods. So, the trick ended up simply being along the lines of:
private void setPrimaryColumnId(Integer id) {...}
private Integer getPrimaryColumnId() {...}
public void setPrimaryColumn(Column column) {...}
#JsonIgnore
public Column getPrimaryColumn() {...}

Xstream: Implicitly ignoring all fields

How do I tell Xstream to serialize only fields which are annotated explicitly and ignore the rest?
I am trying to serialize a hibernate persistent object and all proxy related fields get serialized which I don’t want in my xml.
e.g.
<createdBy class="com..domain.Users " reference="../../values/createdBy"/>
is not something I want in my xml.
Edit: I don’t think I made this question clear. A class may inherit from a base class on which I have no control (as in hibernate’s case) on the base class properties.
public class A {
private String ShouldNotBeSerialized;
}
public class B extends A {
#XStreamAlias("1")
private String ThisShouldbeSerialized;
}
In this case when I serialize class B, the base class field ShouldNotBeSerialized will also get serialized. This is not something I want. In most circumstances I will not have control on class A.
Therefore I want to omit all fields by default and serialize only fields for which I explicitly specify the annotation. I want to avoid what GaryF is doing, where I need to explicitly specify the fields I need to omit.
You can omit fields with the #XstreamOmitField annotation. Straight from the manual:
#XStreamAlias("message")
class RendezvousMessage {
#XStreamOmitField
private int messageType;
#XStreamImplicit(itemFieldName="part")
private List<String> content;
#XStreamConverter(SingleValueCalendarConverter.class)
private Calendar created = new GregorianCalendar();
public RendezvousMessage(int messageType, String... content) {
this.messageType = messageType;
this.content = Arrays.asList(content);
}
}
I can take no credit for this answer, just sharing what I have found. You can override the wrapMapper method of the XStream class to achieve what you need.
This link explains in detail: http://pvoss.wordpress.com/2009/01/08/xstream/
Here is the code you need if you don't want the explanation:
// Setup XStream object so that it ignores any undefined tags
XStream xstream = new XStream() {
#Override
protected MapperWrapper wrapMapper(MapperWrapper next) {
return new MapperWrapper(next) {
#Override
public boolean shouldSerializeMember(Class definedIn,
String fieldName) {
if (definedIn == Object.class) {
return false;
}
return super
.shouldSerializeMember(definedIn, fieldName);
}
};
}
};
You might want to do all your testing before you implement this code because the exceptions thrown by the default XStream object are useful for finding spelling mistakes.
There was already a ticket for the XStream people:
Again, this is by design. XStream is a serialization tool, not a data
binding tool. It is made to serialize Java objects to XML and back. It
will write anything into XML that is necessary to recreate an equal
object graph. The generated XML can be tweaked to some extend by
configuration for convenience, but this is already an add-on. What you
like to do can be done by implementing a custom mapper, but that's a
question for the user's list and cannot be handled here.
http://jira.codehaus.org/browse/XSTR-569
I guess the only direct way is to dive into writing a MapperWrapper and exclude all fields you have not annotated. Sounds like a feature request for XStream.

Categories