In My CXF-based REST layer I am using Jackson for seializing/deserializing Groovy objects. The issue I am experiencing deals with deserializing a property that does not have a setter method. There is a domain object Dashboard with getGroups() method returning a list of Group objects. Upon serialization this object is properly converted to JSON with "group" attribute. When I send the object for update from JavaScript, JSON still has the "group" attribute. Since the property is read-only on the domain object I would like to simply ignore the "group" property when deserializing JSON.
Since I am using Jackson mix-ins, I tried various combination of #JsonIgnore, #JsonGetter and #JsonProperty annotations - all to no avail. If the property is available upon serialization, I get the error below in deserialization. I can clean the JSON object in JavaScript by removing the "group" attribute, bit I would like to find a server-side solution.
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Problem deserializing 'setterless' property 'groups': get method returned null (through reference chain: org.ozoneplatform.commons.server.domain.model.DashboardTemplate["groups"])
at com.fasterxml.jackson.databind.deser.impl.SetterlessProperty.deserializeAndSet(SetterlessProperty.java:114)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:198)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:577)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObjectUsingNonDefault(BeanDeserializer.java:393)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:289)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:121)
at com.fasterxml.jackson.databind.ObjectReader._bind(ObjectReader.java:1169)
at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:625)
at com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider.readFrom(JacksonJsonProvider.java:448)
at org.apache.cxf.jaxrs.utils.JAXRSUtils.readFromMessageBody(JAXRSUtils.java:1038)
at org.apache.cxf.jaxrs.utils.JAXRSUtils.processParameter(JAXRSUtils.java:614)
at org.apache.cxf.jaxrs.utils.JAXRSUtils.processParameters(JAXRSUtils.java:578)
at org.apache.cxf.jaxrs.interceptor.JAXRSInInterceptor.processRequest(JAXRSInInterceptor.java:238)
How can I tell Jackson to ignore a read-only property on deserialization?
Thank you,
Michael
After many fruitless hours, I have finally found the magic combination of spells that addresses such a seemingly trivial issue. In the mixin I had to create this combination of annotations:
#JsonIgnore
abstract Set<Group> groups
#JsonProperty
abstract Set<Group> getGroups()
#JsonIgnore
abstract void setGroups(Set<Group> groups)
On top of that I had to add two configuration parameters to the ObjectMapper:
mapper.configure(MapperFeature.USE_GETTERS_AS_SETTERS, false)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
Isn't there a simpler way to achieve the same?
Michael
I had the same issue. Solution was to give the getter the correct name:
List list...
setList(...)
was correct, but my getter was
setProductList() which produced the "setterLess" error. changing it to:
setList(...) resolved the issue
I just ran into the same problem, and my solution was to create a private, no-op setter:
public class MyFoo {
public String getMyStr() {
return "hello, world";
}
private void setMyStr(String ignored) {}
}
Making setMyStr private prevents me from accidentally trying to call it from my code, but Jackson still finds it and invokes it. Little does jackson know -- or care -- that invoking it does nothing.
Related
I am trying spring-data-rest with spring-data-mongo and a lot of things are working beautifully out of the box, including support for eTag field.
#EnableMongoAuditing annotations works very well too: when a document is created, the #CreatedDate and #LastModifiedDate fields are set.
The problem is that the #CreatedDate field being set to null during updates. I found an unresolved issue Mongo Auditing: #CreatedDate field gets set to null on updates with Spring Data Rest with a suggested workaround of using the #JsonIgnore annotation which does not work for me.
There was also a similar question here which does not appear to be the same issue.
I am using version 1.10.1.RELEASE of spring-data-mongo and 2.6.1.Release of spring-data-rest.
Is there a solution to this issue?
One solution is to tell Jackson to output the field to JSON when serializing the object, but never read the value when deserializing the object, using the access element of JsonProperty:
#Document
public class MyDocument {
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
#CreatedDate
private Instant createdDate;
}
Spring Data REST will still output the createdDate field to JSON, but it will never read from it, including when performing an update.
Note that this will affect the serialization of your document class throughout the entire application. Often this will not be an issue, but it would pose a problem if there are other places in the code that need to be able to deserialize the createdDate from JSON.
Created date make sense only for immutable entities (which you are not going to update)
If entity is updatable, would like to use only last modified instead
For any other cases probably make sense use some history audition log..
#Entity
// ...
public class MyEntity {
// ...
#CreatedDate
private LocalDateTime createdAt; // modifiedAt
#PreUpdate
public void fixSpringDataRestNullDate() {
createdAt = LocalDateTime.now();
}
}
I'm using Objectify and wish to have its Key<> type passed around in my API. I've created an ApiTransformer, but my questions is where to declare it, since the serialized Key<> class is not available, hence I cannot declare its transformer as a class annotation. I tried declaring it in the #Api annotation, but it doesn't work, I still get the error:
There was a problem generating the API metadata for your Cloud Endpoints classes: java.lang.IllegalArgumentException: Parameterized type com.googlecode.objectify.Key<[my package].User> not supported.
The ApiTransformer looks like:
public class KeyTransformer implements Transformer<Key<?>, String> {
public String transformTo(Key<?> in) {
return in.getString();
}
public Key<?> transformFrom(String in) {
return Key.valueOf(in);
}
}
And in my #Api I have:
#Api(name = "users", version = "v1",transformers = {KeyTransformer.class})
Unfortunately you can't. As you said you need to declare it on the Key class, your only chances to make this work are either.
1) Recompile the Key class for objectify with the #transformer annotation.
2) Extend the Key class with your own implementation and define the transformer there.
I don't really like any of those options so the way i usually resolve this is to hide the key object getter (by using #ApiResourceProperty(ignored=AnnotationBoolean.TRUE)) and only expose the id from that key.
That way you get a Endpoints frendly object, the only downside is you'll have to reconstitute the key using Key.create(YourClass.class, longId) manually whenever you need it.
You can add transforms to 3rd party classes by listing the transform in #Api annotation. I'm not dead sure it'll work parameterized class, but I don't see why not.
https://cloud.google.com/appengine/docs/java/endpoints/javadoc/com/google/api/server/spi/config/Api#transformers()
Is there any possibility to ignore field for JSON serialization (for web display) but not for mongo (internal serialization) ?
I`ve tried so far all these methods but field was also ignored for Mongo, or not ignored in both in case of some variations
Jackson: how to prevent field serialization
Ok, I finally solved this.
objectMapper.writerWithView(Views.Public.class).writeValueAsString(lo));
writeValueUsingView is from another version of Jackson, so it wasn't working
Custom serialization for web/mongo can be solved by using #JsonView annotations, try along these lines:
class Views {
static class OnAllViews {}
static class OnlySomeViews extends OnAllViews {}
...
}
public class Thing {
#JsonView(Views.OnAllViews.class) Integer id;
#JsonView(Views.OnlySomeViews.class) String name;
}
and then you can call the appropriate level of serialization through writeValueUsingView method.
objectMapper.writeValueUsingView(out, beanInstance, ViewsPublic.class);
You can read more about it here.
I'm using XmlSerializer. I've had no problems with it until now. I updated Silverlight from 4 to 5 and at the same time also updated the WCF RIA Services from v1 SP1 to v1 SP2. Now the following line gives me an error.
XmlSerializer s = new XmlSerializer(typeof(MyCustomObject));
The error is:
System.InvalidOperationException: System.ServiceModel.DomainServices.Client.EntityConflict cannot be serialized because it does not have a parameterless constructor.
The object I'm using (MyCustomObject in the sample) has not changed in any way so I'm starting to think it's either SL5 or the new RIA Services that is breaking my code. I didn't find any breaking changes document or mentions that this could happen. I don't know why it has a problem with EntityConflict since I'm not using any entities within my object.
Has anyone seen an error like this and/or know how to solve it?
UPDATE!
The final property that the error message says before EntityConflict is an Entity. I think that makes a difference but it has been working before. I'd also like to know why the serializer already tries to serialize the object in the constructor?
public static XmlSerializer GetEntityXmlSerializer<TEntity>()
where TEntity : Entity
{
XmlAttributes ignoreAttribute = new XmlAttributes()
{
XmlIgnore = true,
};
// use base class of Entity,
// if you use type of implementation
// you will get the error.
Type entityType = typeof(Entity);
var xmlAttributeOverrides = new XmlAttributeOverrides();
xmlAttributeOverrides.Add(entityType, "EntityConflict", ignoreAttribute);
xmlAttributeOverrides.Add(entityType, "EntityState", ignoreAttribute);
return new XmlSerializer(typeof(TEntity), xmlAttributeOverrides);
}
I am not sure why this would be happening, RIA Services entities are not XmlSerializable objects and the entities themselves are not decorated with the [Serializable] attribute. Have you added partial classes on the client side which decorate the entities with [Serializable] or modified the code generation in some way?
I got around this problem by using intermediary serializable POCO objects which were copies of my custom objects (which were inherited from Entity). The POCO objects did not inherit from Entity. I just updated their values from the original Entity objects. They then serialized quite nicely. Of course, when you de-serialize you need to update your Entity objects from the POCO objects.
RIA Services is returning a list of Entities that won't allow me to add new items. Here are what I believe to be the pertinent details:
I'm using the released versions of Silverlight 4 and RIA Services 1.0 from mid-April of 2010.
I have a DomainService with a query method that returns List<ParentObject>.
ParentObject includes a property called "Children" that is defined as List<ChildObject>.
In the DomainService I have defined CRUD methods for ParentObject with appropriate attributes for the Query, Delete, Insert, and Update functions.
The ParentObject class has an Id property marked with the [Key] attribute. It also has the "Children" property marked with the attributes [Include], [Composition], and [Association("Parent_Child", "Id",
"ParentId")].
The ChildObject class has an Id marked with the [Key] attribute as well as a foreign key, "ParentId", that contains the Id of the parent.
On the client side, data is successfully returned and I assign the results of the query to a PagedCollectionView like this:
_pagedCollectionView = new PagedCollectionView(loadOperation.Entities);
When I try to add a new ParentObject to the PagedCollectionView like this:
ParentObject newParentObject = (ParentObject)_pagedCollectionView.AddNew();
I get the following error:
" 'Add New' is not allowed for this view."
On further investigation, I found that _pagedCollectionView.CanAddNew is "false" and cannot be changed because the property is read-only.
I need to be able to add and edit ParentObjects (with their related children, of course) to the PagedCollectionView. What do I need to do?
I was just playing around with a solution yesterday and feel pretty good about how it works. The reason you can't add is the source collection (op.Entities) is read-only. However, even if you could add to the collection, you'd still want to be adding to the EntitySet as well. I created a intermediate collection that takes care of both these things for me.
public class EntityList<T> : ObservableCollection<T> where T : Entity
{
private EntitySet<T> _entitySet;
public EntityList(IEnumerable<T> source, EntitySet<T> entitySet)
: base(source)
{
if (entitySet == null)
{
throw new ArgumentNullException("entitySet");
}
this._entitySet = entitySet;
}
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
if (!this._entitySet.Contains(item))
{
this._entitySet.Add(item);
}
}
protected override void RemoveItem(int index)
{
T item = this[index];
base.RemoveItem(index);
if (this._entitySet.Contains(item))
{
this._entitySet.Remove(item);
}
}
}
Then, I use it in code like this.
dataGrid.ItemsSource = new EntityList<Entity1>(op.Entities, context.Entity1s);
The only caveat is this collection does not actively update off the EntitySet. If you were binding to op.Entities, though, I assume that's what you'd expect.
[Edit]
A second caveat is this type is designed for binding. For full use of the available List operation (Clear, etc), you'd need to override a few of the other methods to write-though as well.
I'm planning to put together a post that explains this a little more in-depth, but for now, I hope this is enough.
Kyle
Here's a workaround which I am using:
Instead of using the AddNew, on your DomainContext you can retrieve an EntitySet<T> by saying Context.EntityNamePlural (ie: Context.Users = EntitySet<User> )
You can add a new entity to that EntitySet by calling Add() and then Context.SubmitChanges() to send it to the DB. To reflect the changes on the client you will need to Reload (Context.Load())
I just made this work about 15mins ago after having no luck with the PCV so I am sure it could be made to work better, but hopefully this will get you moving forward.
For my particular situation, I believe the best fit is this (Your Mileage May Vary):
Use a PagedCollectionView (PCV) as a wrapper around the context.EntityNamePlural (in my case, context.ParentObjects) which is an EntitySet. (Using loadOperation.Entities doesn't work for me because it is always read-only.)
_pagedCollectionView = new PagedCollectionView(context.ParentObjects);
Then bind to the PCV, but perform add/delete directly against the context.EntityNamePlural EntitySet. The PCV automatically syncs to the changes done to the underlying EntitySet so this approach means I don't need to worry about sync issues.
context.ParentObjects.Add();
(The reason for performing add/delete directly against the EntitySet instead of using the PCV is that PCV's implementation of IEditableCollectionView is incompatible with EntitySet causing IEditableCollectionView.CanAddNew to be "false" even though the underlying EntitySet supports this function.)
I think Kyle McClellan's approach (see his answer) may be preferred by some because it encapsulates the changes to the EntitySet, but I found that for my purposes it was unneccessary to add the ObservableCollection wrapper around loadOperation.Entities.
Many thanks to to Dallas Kinzel for his tips along the way!