Jackson + FAIL_ON_MISSING_CREATOR_PROPERTIES + Lombok - jackson

I'm attempting to use a Jackson flag on the objectMapper
objectMapper.enable(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES);
This should cause object deserialization to fail if a constructor argument is not set in the json. i.e. If a field is missing as opposed to being set to null.
But I noticed that it only works if the object I want to deserialize has a constructor like so
public MyObject(#JsonProperty("id") UUID id, #JsonProperty("url") URL url) {
this.id = id;
this.url = url;
}
That's a little problematic as I'd hoped to use lombok's #AllArgsConstructor to generate the constructor. But if the constructor is missing the #JsonProperty(..) the FAIL_ON_MISSING_CREATOR_PROPERTIES check does not work. Instead the parameters are passed in as null.
I've come across some solutions here Can't make Jackson and Lombok work together. But so far they're not working for me.
Any suggestions?
--- Update ---
The annotations on my class are
#Data
#Builder
#ToString
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
#JsonInclude(JsonInclude.Include.NON_DEFAULT)
public class MyClass { ... }

The following combination of annotations work fine with Lombok 1.18.0 and Jackson 2.9 (the most recent versions as of July 2018):
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public static class Foo {
private UUID id;
private String url;
}
String json = "{\n" +
" \"id\": \"32783be3-5355-41d2-807b-619e3481d220\",\n" +
" \"url\": \"http://example.com\"\n" +
"}";
ObjectMapper mapper = new ObjectMapper();
Foo foo = mapper.readValue(json, Foo.class);

Related

Apache Isis: #Property(editing = Editing.ENABLED) doesn't work for ViewModels

I added a property to ViewModel and marked it with Editing.ENABLED.
#DomainObject(
nature = Nature.VIEW_MODEL,
objectType = "homepage.HomePageViewModel"
)
public class HomePageViewModel {
#Setter #Getter
#Property(editing = Editing.ENABLED)
private String editableField;
}
But this field is not editable on UI:
But it works fine for SimpleObject:
Does it work correctly for ViewModel?
Maybe ViewModel shouldn't have any properties?
No, it isn't working correctly for view models... the framework is meant to support this.
The good news is that there is a workaround. If you annotate the class to use the (more flexible) JAXB-style of view model, then it all works as expected.
Here's an updated version of the class; look for annotations starting #Xml...:
#XmlRootElement(name = "compareCustomers")
#XmlType(
propOrder = {
"editableField"
}
)
#XmlAccessorType(XmlAccessType.FIELD)
public class HomePageViewModel {
#XmlElement(required = true)
#Setter #Getter
#Property(editing = Editing.ENABLED)
private String editableField;
public TranslatableString title() {
return TranslatableString.tr("{num} objects", "num", getObjects().size());
}
public List<SimpleObject> getObjects() {
return simpleObjectRepository.listAll();
}
#XmlTransient
#javax.inject.Inject
SimpleObjectRepository simpleObjectRepository;
}
For more on JAXB view models, see the user guide.
Meantime I've raised a JIRA ticket for the issue you've discovered,

jackson custom serialization with filtering

I need to customize serialization of a POJO in Jackson so that I can apply filter on the properties based on user input
I applied the following annotations on the POJO.
#JsonFilter("userFilter")
#JsonSerialize(using = UserSerializer.class)
The custom serializer class is as below.
public class UserSerializer extends JsonSerializer<User> {
#Override
public void serialize(User value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
SimpleFilterProvider sfp = new SimpleFilterProvider();
// create a set that holds name of User properties that must be serialized
Set userFilterSet = new HashSet<String>();
userFilterSet.add("firstName");
userFilterSet.add("corporateOrgs");
userFilterSet.add("rights");
userFilterSet.add("requirements");
sfp.addFilter("userFilter",SimpleBeanPropertyFilter.filterOutAllExcept(userFilterSet));
// create an objectwriter which will apply the filters
ObjectWriter writer = mapper.writer(sfp);
String json = writer.writeValueAsString(value);
}
}
I can see that Jackson is trying to serialize the POJO using the custom serializer defined. However it ends up in infinite recursion/stackoverflow as writer.writeValueAsString(value) ends up calling the custom serializer again.
Obviously I have not got some basic stuff right here. If the filtering is done outside the serialize method (for example in a method called from main() ), filtering works as expected.
can anyone please provide insight/link to documentation on how to make use of custom serialization to leverage filtering.
Fields can be filtered out with JsonFilter, or you can create a custom JsonSerialize serializer that writes out only certain fields.
Independent of the use of a JsonFilter, the attempt to recursively reserialize the same object to be serialized (first parameter of the overwritten serialize method) in a user-defined serializer with an object mapper will result in an infinite loop. Instead, in a custom serializer you would rather use the JsonGenerator methods (second parameter of the overridden serialize method) to write out field name/values.
In the following answer both variants (#JsonFilter and #JsonSerialize) are demonstrated, where only a part of the available fields are serialized to JSON.
#JsonFilter
To apply filters to properties based on user input, you do not need to extend JsonSerializer. Instead, you annotate the POJO with JsonFilter and just apply the filtering.
A self-contained example based on your code would look like this:
package com.software7.test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Main m = new Main();
try {
m.serialize();
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
void serialize() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
SimpleFilterProvider sfp = new SimpleFilterProvider();
Set<String> userFilterSet = new HashSet<>();
userFilterSet.add("firstName");
userFilterSet.add("corporateOrgs");
userFilterSet.add("rights");
userFilterSet.add("requirements");
sfp.addFilter("UserFilter",
SimpleBeanPropertyFilter.filterOutAllExcept(userFilterSet));
mapper.setFilterProvider(sfp);
User user = new User("Brownrigg", "Don", "none", "+rwx", "n/a",
"some", "superfluous", "properties");
System.out.println(user);
System.out.println(">>>> serializing >>>>");
String s = mapper.writeValueAsString(user);
System.out.println(s);
}
}
User POJO
package com.software7.test;
import com.fasterxml.jackson.annotation.JsonFilter;
#JsonFilter("UserFilter")
public class User {
public String lastName;
public String firstName;
public String corporateOrgs;
public String rights;
public String requirements;
public String a, b, c;
public User(String lastName, String firstName, String corporateOrgs, String rights, String requirements,
String a, String b, String c) {
this.lastName = lastName;
this.firstName = firstName;
this.corporateOrgs = corporateOrgs;
this.rights = rights;
this.requirements = requirements;
this.a = a;
this.b = b;
this.c = c;
}
#Override
public String toString() {
return "User{" +
"lastName='" + lastName + '\'' +
", firstName='" + firstName + '\'' +
", corporateOrgs='" + corporateOrgs + '\'' +
", rights='" + rights + '\'' +
", requirements='" + requirements + '\'' +
", a='" + a + '\'' +
", b='" + b + '\'' +
", c='" + c + '\'' +
'}';
}
}
Test
The debug output of the above program would look like this:
User{lastName='Brownrigg', firstName='Don', corporateOrgs='none', rights='+rwx', requirements='n/a', a='some', b='superfluous', c='properties'}
>>>> serializing >>>>
{"firstName":"Don","corporateOrgs":"none","rights":"+rwx","requirements":"n/a"}
The test is successful! As you can see, the properties lastName, a, b and c are removed.
#JsonSerialize Alternative
If you want to use a customer serializer instead you can do like so:
Replace the annotation:
#JsonFilter("UserFilter")
with
#JsonSerialize(using = UserSerializer.class)
but do not use both.
The UserSerializer class could look like this:
package com.software7.test;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
public class UserSerializer extends JsonSerializer<User> {
#Override
public void serialize(User user, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws IOException, JsonProcessingException {
jsonGenerator.writeStartObject();
jsonGenerator.writeObjectField("firstName", user.firstName);
jsonGenerator.writeObjectField("corporateOrgs", user.corporateOrgs);
jsonGenerator.writeObjectField("rights", user.rights);
jsonGenerator.writeObjectField("requirements", user.requirements);
jsonGenerator.writeEndObject();
}
}
Finally, the serialization method would look like this:
void serialize() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
User user = new User("Brownrigg", "Don", "none", "+rwx", "n/a",
"some", "superfluous", "properties");
System.out.println(user);
System.out.println(">>>> serializing >>>>");
String s = mapper.writeValueAsString(user);
System.out.println(s);
}
The result would be the same in this example. Which variant is better suited depends on the specific use case or personal preferences.

Serialization of Keys with Circular References using Jackson

I am trying to serialize a HashMap from Objects to Strings, but the specific Object has a reference to the current class leading to an infinite recursion, which doesn't seem to be solved with the usual JsonIdentifyInfo annotation. Here's an example:
public class CircularKey {
public void start() throws IOException {
ObjectMapper mapper = new ObjectMapper();
Cat cat = new Cat();
// Encode
String json = mapper.writeValueAsString(cat);
System.out.println(json);
// Decode
Cat cat2 = mapper.readValue(json, Cat.class);
System.out.println(mapper.writeValueAsString(cat2));
}
}
#JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "#id")
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "#class")
class Mouse {
int id;
#JsonProperty
Cat cat;
}
#JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "#id")
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "#class")
class Cat {
int id;
#JsonSerialize(keyUsing = MouseMapKeySerializer.class)
#JsonDeserialize(keyUsing = MouseMapKeyDeserializer.class)
#JsonProperty
HashMap<Mouse, String> status = new HashMap<Mouse, String>();
public Cat() {
Mouse m = new Mouse();
m.cat = this;
status.put(m, "mike");
}
}
Here's the serializer/deserializer for the key:
class MouseMapKeySerializer extends JsonSerializer<Mouse> {
static ObjectMapper mapper = new ObjectMapper();
#Override
public void serialize(Mouse value, JsonGenerator generator,
SerializerProvider provider) throws IOException,
JsonProcessingException {
String json = mapper.writeValueAsString(value);
generator.writeFieldName(json);
}
}
class MouseMapKeyDeserializer extends KeyDeserializer {
static ObjectMapper mapper = new ObjectMapper();
#Override
public Mouse deserializeKey(String c, DeserializationContext ctx)
throws IOException, JsonProcessingException {
return mapper.readValue(c, Mouse.class);
}
}
If I switch the map to HashMap (String,Object) it works but I cannot change the original mapping. Any ideas?
It looks like you found your answer at http://jackson-users.ning.com/forum/topics/serializing-hashmap-with-object-key-and-recursion. This doesn't seem to be possible because:
Complex keys are tricky, and it is not a use case I ever considered. Then again, there is nothing specifically preventing use of standard components; main concern was just the limitations than JSON has (must be String-value, JsonParser/JsonGenerator expose keys as different tokens).
There is no explicit support for either polymorphic types or object ids for Object keys. Standard serializers/deserializers are mostly for relatively simple types that can be easily and reliably converted to/from Strings; numbers, Dates, UUIDs.
So: unlike with value handlers, where modular design (with separation of TypeSerializer/JsonSerializer) makes sense, I think what you need to do is to have custom (de)serializers that handle all aspects. You should be able to use code from existing value (de)serializers, type (de)serializers, but not classes themselves.
Your use case does sound interesting, but for better or worse, it is pushing the envelope quite a bit. :-)

Cannot populate POJO with JSON using Jackson

I'm trying to populate a POJO from a JSON that doesn't really match in any way and am having trouble getting this resolved. I can't change the JSON since it is an external service but I maybe able to modify the POJO if needed.
Below is an example JSON:
{"Sparse":[{"PixId":1,"PixName":"SWE","Description":"Unknown"},{"PixId":2,"PixName":"PUMNW","Description":"Power Supplement"}],"Status":0,"Message":null}
Below is the POJO:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Pix {
#JsonProperty("Description")
private String description;
#JsonProperty("PixId")
private int pixId;
#JsonProperty("PixName")
private String pixName;
// getters and setters
}
And here is my code to do the conversion:
ObjectMapper om = new ObjectMapper();
om.configure(DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
om.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
List<Pix> pixList = om.readValue(pixJson, new TypeReference<List<Pix>>() {});
The pixList contains only 1 element (should be 2 using the JSON above) and all the properties have not been populated. I'm using Jackson 1.9.9. Any ideas on how to get this to work? TIA.
You have to create new POJO class for main object which contains the List<Pix>. It could looks like this:
class Root {
#JsonProperty("Status")
private int status;
#JsonProperty("Message")
private String message;
#JsonProperty("Sparse")
private List<Pix> sparse;
//getters/setters
}
And now your deserialization code could looks like this:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
List<Pix> pixList = mapper.readValue(pixJson, Root.class).getSparse();

Why do not #JsonTypeInfo work together with #JsonIdentityInfo?

#JsonIdentityInfo works as expected with the following classes:
Baseclass:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "uuid")
public class TestEntityBas {
#JsonProperty
public String uuid = "0001";
}
Subclass:
public class TestEntityGoa extends TestEntityBas {
#JsonProperty
public String texten = "This is text!";
}
Container class:
public class TestEntity {
#JsonProperty
String stringer = "Hej hopp!";
#JsonIdentityReference(alwaysAsId = true)
public TestEntityGoa goa = new TestEntityGoa();
}
The result is as expected:
{"stringer":"Hej hopp!","goa":"0001"}
When I add #JsonTypeInfo to the base class like this:
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="#class")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "uuid")
public class TestEntityBas {
#JsonProperty
public String uuid = "0001";
}
Now the entire TestEntityGoa get serialized like this:
{"stringer":"Hej hopp!","goa":{"#class":"com.fodolist.model.TestEntityGoa","uuid":"0001","texten":"This is text!"}}
I expect the first result even when I use #JsonTypeInfo and #JsonIdentityInfo in the same class. What am I doing wrong?
I can't see anything obviously wrong here, so you may have found a bug. Combination of type and identity info is bit tricky to handle so there may be edge cases that do not yet work as intended, so could you file a bug at Github issue tracker for this?