Skip to content

Commit

Permalink
[#1040] Fix collection deserialization issues with Jackson
Browse files Browse the repository at this point in the history
  • Loading branch information
beikov committed Apr 12, 2020
1 parent a90846b commit 869f65e
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Not yet released
* Only expose `Serializable` `EntityViewManager` wrapper in special methods or callbacks
* Support `@PostCreate` and other lifecycle methods with default and even private visibility
* Support `@ViewFilter` inheritance
* Fix collection deserialization issues with Jackson

### Backwards-incompatible changes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
Expand All @@ -33,6 +34,7 @@

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;

/**
* @author Christian Beikov
Expand Down Expand Up @@ -61,6 +63,10 @@ public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, Bean
}
});
objectMapper.registerModule(module);
// We need this property, otherwise Jackson thinks it can use non-visible setters as mutators
objectMapper.configure(MapperFeature.INFER_PROPERTY_MUTATORS, false);
// Obviously, we don't want fields to be set which are final because these are read-only
objectMapper.configure(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS, false);
objectMapper.setVisibility(new VisibilityChecker.Std(JsonAutoDetect.Visibility.DEFAULT) {
@Override
public boolean isGetterVisible(Method m) {
Expand All @@ -81,6 +87,20 @@ public boolean isIsGetterVisible(Method m) {
public boolean isIsGetterVisible(AnnotatedMethod m) {
return metamodel.managedView(m.getDeclaringClass()) == null && super.isGetterVisible(m);
}

// We make setters for collections "invisible" so that is uses a SetterlessProperty to always merge values into existing collections

@Override
public boolean isSetterVisible(Method m) {
Class<?> rawParameterType;
return super.isSetterVisible(m) && !Collection.class.isAssignableFrom(rawParameterType = m.getParameterTypes()[0]) && !Map.class.isAssignableFrom(rawParameterType);
}

@Override
public boolean isSetterVisible(AnnotatedMethod m) {
Class<?> rawParameterType;
return super.isSetterVisible(m) && !Collection.class.isAssignableFrom(rawParameterType = m.getRawParameterType(0)) && !Map.class.isAssignableFrom(rawParameterType);
}
});

this.objectMapper = objectMapper;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Set;

/**
* @author Christian Beikov
Expand Down Expand Up @@ -233,4 +234,49 @@ interface CreatableAndUpdatableViewWithNested {
CreatableAndUpdatableViewWithSetters getParent();
void setParent(CreatableAndUpdatableViewWithSetters parent);
}

@Test
public void testCreatableWithCollection() throws Exception {
EntityViewAwareObjectMapper mapper = mapper(CreatableWithCollection.class, CreatableAndUpdatableViewWithSetters.class, NameView.class);
ObjectReader objectReader = mapper.readerFor(mapper.getObjectMapper().constructType(CreatableWithCollection.class));
CreatableWithCollection view = objectReader.readValue("{\"name\": \"test\", \"children\": [{\"name\": \"parent\"}]}");
Assert.assertTrue(((EntityViewProxy) view).$$_isNew());
Assert.assertEquals("test", view.getName());
Assert.assertEquals(mapper.getEntityViewManager().getChangeModel(view).get("children").getInitialState(), view.getChildren());
Assert.assertEquals(1, view.getChildren().size());
}

@EntityView(SomeEntity.class)
@CreatableEntityView
@UpdatableEntityView
interface CreatableWithCollection {
@IdMapping
long getId();
String getName();
void setName(String name);
Set<CreatableAndUpdatableViewWithSetters> getChildren();
}

@Test
public void testCreatableWithCollectionWithSetter() throws Exception {
EntityViewAwareObjectMapper mapper = mapper(CreatableWithCollectionWithSetter.class, CreatableAndUpdatableViewWithSetters.class, NameView.class);
ObjectReader objectReader = mapper.readerFor(mapper.getObjectMapper().constructType(CreatableWithCollectionWithSetter.class));
CreatableWithCollectionWithSetter view = objectReader.readValue("{\"name\": \"test\", \"children\": [{\"name\": \"parent\"}]}");
Assert.assertTrue(((EntityViewProxy) view).$$_isNew());
Assert.assertEquals("test", view.getName());
Assert.assertEquals(mapper.getEntityViewManager().getChangeModel(view).get("children").getInitialState(), view.getChildren());
Assert.assertEquals(1, view.getChildren().size());
}

@EntityView(SomeEntity.class)
@CreatableEntityView
@UpdatableEntityView
interface CreatableWithCollectionWithSetter {
@IdMapping
long getId();
String getName();
void setName(String name);
Set<CreatableAndUpdatableViewWithSetters> getChildren();
void setChildren(Set<CreatableAndUpdatableViewWithSetters> children);
}
}

0 comments on commit 869f65e

Please sign in to comment.