Jackson single argument constructor with single argument fails with ParameterNameModule - java

I am using Jackson 2.8.5 with ParameterNamesModule for Java 8 (https://github.com/FasterXML/jackson-modules-java8).
My problem is very specific for one use case, when I want to de-serialize a class with a single constructor using a single argument. Here is a test to reproduces the behavior:
public class JacksonTest {
#Test
public void TestReadValue() throws IOException {
ObjectMapper objectMapper = new ObjectMapper()
.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
.setVisibility(PropertyAccessor.CREATOR, JsonAutoDetect.Visibility.PUBLIC_ONLY)
.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES));
ImmutableIdentity identity = objectMapper.readValue("{\"id\":\"ABCDEF\"}", ImmutableIdentity.class);
assertEquals("ABCDEF", identity.id);
}
private static final class ImmutableIdentity {
private final String id;
public ImmutableIdentity(final String id) {
Objects.requireNonNull(id, "The id must not be null.");
this.id = id;
}
}
}
The test fails with the reason:
com.fasterxml.jackson.databind.JsonMappingException: Can not construct
instance of JacksonTest$ImmutableIdentity, problem: The id must not be
null. at [Source: {"id":"ABCDEF"}; line: 1, column: 15]
The funny thing is that if I add another argument to the constructor, the test passes OK.
public class JacksonTest {
#Test
public void TestReadValue() throws IOException {
ObjectMapper objectMapper = new ObjectMapper()
.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
.setVisibility(PropertyAccessor.CREATOR, JsonAutoDetect.Visibility.PUBLIC_ONLY)
.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES));
ImmutableIdentity identity = objectMapper.readValue("{\"id\":\"ABCDEF\"}", ImmutableIdentity.class);
assertEquals("ABCDEF", identity.id);
}
private static final class ImmutableIdentity {
private final String id;
public ImmutableIdentity(final String id, **final String unused**) {
Objects.requireNonNull(id, "The id must not be null.");
this.id = id;
}
}
}
I really don't like the idea to use a useless argument in the constructor here to make it less ambiguous, because it has no value in my business objects, especially they are for instance ProjectId, or some abstract Id that defines my entities and I need to construct them manually as well. So I would like to find a configuration of Jackson to support this but I could not.
I also crossposted here for the maintainers: https://github.com/FasterXML/jackson-modules-java8/issues/8

Are you by any chance compiling JacksonTest with -parameters option? If so, this is expected behavior.
Single argument constructors were historically used as delegating creators.
I've had discussions about this matter with #staxman even when we created the module. The issue popped up a number of times by various users, see this issue for details.
Looking to the future, this will hopefully be changed in 3.0, see this topic for details.
Update: regarding 3.0 change, see this issue. If you want this behavior to change please add +1 or comment. Right now it isn't clear if either approach is better as there are users that need the old behavior (see the issue for more details).

Related

Spring Boot - how to validate fields that depend on each other?

Is there some way in Spring Boot that I can perform validation on properties that depend on each other's values, and have the error message be associated with the property?
I want to return the errors to the user in a nice JSON structure:
{
"errors": {
"name": "is required if flag is true"
}
}
Example:
#Entity
public class MyEntity {
private boolean nameRequiredFlag;
// Required if "nameRequiredFlag" is set to true:
private String name;
}
One solution that doesn't solve my problem of associating the error message with the name property is to create a validator annotation for the entity:
#ValidEntity
public class MyEntity {
private boolean nameRequiredFlag;
// Required if "nameRequiredFlag" is set to true:
private String name;
}
#Constraint( validatedBy = { MyEntityValidator.class } )
#Documented
#Target( { ElementType.TYPE } )
#Retention( RetentionPolicy.RUNTIME )
public #interface ValidEntity{
Class<?>[] groups () default {};
String message () default "name is required if 'nameRequiredFlag' is true";
Class<? extends Payload>[] payload () default {};
}
public class MyEntityValidator implements Validator<ValidEntity, MyEntity> {
#Override
public boolean isValid ( MyEntity entity, ConstraintValidatorContext context ) {
if ( !entity.nameRequiredFlag ) return true;
return !StringUtils.isBlank( entity.getName() );
}
}
This is laughably cumbersome and doesn't solve my problem. Isn't there any way I can do this with the framework validation?
Edit: This is for a JSON API, and the consumer really needs to be able to associate the error message to a best guess at which field has an issue. It is not helpful to send the consumer an error message for the whole object, or a computed property.
Solution given by #EvicKhaosKat is one way of doing it. However, when there are too many fields dependent on each other in a complicated way, your class becomes full of annotations and I personally struggle a lot relating them.
A simpler approach is to create a method(s) in your pojo which does the cross field validations and returns a boolean. On the top of this method annotate it with #AssertTrue(message = "your message"). It will solve your problem in a cleaner fashion.
public class SampleClass {
private String duration;
private String week;
private String month;
#AssertTrue(message = "Duration and time attributes are not properly populated")
public boolean isDurationCorrect() {
if (this.duration.equalsIgnoreCase("month")) {
if (Arrays.asList("jan", "feb", "mar").contains(month))
return true;
}
if (this.duration.equalsIgnoreCase("week")) {
if (Arrays.asList("1-7", "8-15", "16-24", "25-31").contains(week))
return true;
}
return false;
}
}
Note: I have not tested this code but have used this approach in multiple places and it works.
Possible reason is that name validation operates on not-yet-fully constructed object, so nameRequiredFlag is not filled yet.
As an option there is a #GroupSequence annotation, which allows to group and perform validations in an order you specify.
For example it is possible to add to MyEntity annotations:
#ValidEntity(groups = DependentValidations.class)
#GroupSequence({MyEntity.class, DependentValidations.class})
So all the other validation annotations on MyEntity class gonna be performed first, and after that DependentValidations group, which consists of ValidEntity.
Thus ValidEntity will be called on fully created object, and the last in order.
(DependentValidations.class - just an empty interface created somewhere nearby, like any other marker interface)
https://www.baeldung.com/javax-validation-groups will possibly describe that in much more details.
p.s. answer provided by #Innovationchef will possibly suit the case more :)

Lombok's `#Builder` annotation stubbornly stays package - private

I have the following #Builder - annotated class:
#Data
#Builder(access = AccessLevel.PUBLIC)
#Entity
public class LiteProduct
{
// Minimal information required by our application.
#Id
private Long id; // Unique
private String name;
private Long costInCents;
private String type;
// Model the product types as a Hash Set in case we end up with several
// and need fast retrieval.
public final static Set<String> PRODUCT_TYPES = new HashSet<>
(Arrays.asList("flower", "topical", "vaporizer", "edible", "pet"));
// Have to allow creation of products without args for Entities.
public LiteProduct()
{
}
public LiteProduct(#NonNull final Long id, #NonNull final String name,
#NonNull final String type, #NonNull final Long costInCents)
{
if(!PRODUCT_TYPES.contains(type))
{
throw new InvalidProductTypeException(type);
}
this.name = name;
this.id = id;
this.costInCents = costInCents;
}
Whenever I want to use the builder class that Lombok is purported to give me, despite the fact that the IDE seems to detect it just fine:
I get a compile-time error about its visibility:
I have looked at some workarounds such as this or this, and they all seem to imply that my problem ought to already be solved automatically and that Lombok by default produces public Builder classes. This does not seem to be implied from my output, and does not happen even after I put the parameter access=AccessLevel.PUBLIC in my #Builder annotation in LiteProduct. Any ideas? Is there something wrong with the fact that this class is also an #Entity? Something else I'm not detecting?
// Edit: I determined that when I move the class in the same package as the one I am calling the builder pattern from, it works just fine. This is not an #Entity issue, but a package visibility issue which based on what I'm reading should not be there.
The problem was that I was using the following line of code to create an instance of LiteProduct:
return new LiteProduct.builder().build();
instead of:
return LiteProduct.builder().build();
which is what the #Builder annotation allows you to do. Clearly builder() is like a factory method for Builders that already calls new for you.

How to serialize differently the same properties of the same entity using jackson

Suppose you have this entity:
class Foo{
String propA;
String propB;
}
and you want to serialize for one API like :
{propA: "ola",
propB: "Holla"}
and for another API like :
{fooPropA: "ola",
fooPropB: "Holla"}
How can this be achieved using jackson and using the same entity. Creating 2 different entities is not an option :)
There are several ways in which you can achieve this. You can enable a custom serializer (already covered by #se_vedem), register an annotation introspector which changes the property names for the corresponding class and so on.
However, if you are willing to only add a string prefix to all the property names, then the Jackson property name strategy is probably the best fit. The naming strategy class has the access to the serialized object type information, so you can make a decision whether to change the property name or not.
Here is an example using a custom annotation that defines the prefix:
public class JacksonNameStrategy {
#Retention(RetentionPolicy.RUNTIME)
public static #interface PropertyPrefix {
String value();
}
#PropertyPrefix("foo_")
public static class Foo {
public String propA;
public String propB;
public Foo(String propA, String propB) {
this.propA = propA;
this.propB = propB;
}
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new MyPropertyNamingStrategyBase());
System.out.println(mapper.writeValueAsString(new Foo("old", "Holla")));
}
private static class MyPropertyNamingStrategyBase extends PropertyNamingStrategy {
#Override
public String nameForField(MapperConfig<?> config,
AnnotatedField field,
String defaultName) {
PropertyPrefix ann = field.getDeclaringClass().getAnnotation(PropertyPrefix.class);
if (ann != null) {
return ann.value() + defaultName;
}
return super.nameForField(config, field, defaultName);
}
}
}
Output:
{"foo_propA":"old","foo_propB":"Holla"}
In your API method you choose between two ObjectMapper instances one with the default naming naming strategy and one with the custom one.
You can achieve this by using modules feature from Jackson.
Basically, each API would have it's own ObjectMapper and they will be configured with different modules. This way you can create 2 serializers for the same class and register them on the appropriate module. More read can be found here http://wiki.fasterxml.com/JacksonFeatureModules
However, be aware that serializers are loaded in a particular order. First it tries to get the annotated ones, if none is found it will try to get those registered from modules. So, for example if you have your class annotated with serializer, then that serializer(FooSerializer) would be chosen instead of the one configured in module(MySecondFooSerializer).
#JsonSerialize(using = FooSerializer.class)
class Foo{
String propA;
String propB;
}
module.addSerializer(Foo.class, new MySecondFooSerializer());

Change field case with an ObjectMapper

I think I need to create a specialist ObjectMapper and cannot find any sample code to start the process.
The creator of the JSON is using .Net and public properties and therefore uses field names with an uppercase initial. I am parsing the JSON into POJOs so I would like to use a lowercase initial.
At their end:
public class Facet
{
public string Name { get; set; }
public string Value { get; set; }
}
At my end I must therefore have:
public class Facet {
public String Name;
public String Value;
}
I would much prefer:
public class Facet {
public String name;
public String value;
}
Am I right that this could be done with an ObjectMapper?
Your first issue can be addressed very simply with the #JsonProperty annotation:
// java-side class
public class Facet
{
#JsonProperty("Name")
public String name;
#JsonProperty("Value")
public String value;
}
Now the ObjectMapper will match up the differently-cased field names. If you don't want to add annotations into your classes, you can create a Mix-in class to stand in for your Facet:
public class FacetMixIn
{
#JsonProperty("Name")
public String name;
#JsonProperty("Value")
public String value;
}
objectMapper.getDeserializationConfig().addMixInAnnotations(Facet.class, FacetMixIn.class);
This will achieve the same thing, without requiring additional annotations in your Facet class.
Instead of annotating each field, the Jackson ObjectMapper can be configured to use a built-in or custom PropertyNamingStrategy, to apply a consistent translation between Java property/field names and JSON element names.
For example:
myObjectMapper.setPropertyNamingStrategy(PascalCaseStrategy);
This problem could be solved from Jackson 2.5.0 like this:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
From the javadoc:
com.fasterxml.jackson.databind.MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES
Feature that will allow for more forgiving deserialization of incoming
JSON. If enabled, the bean properties will be matched using their
lower-case equivalents, meaning that any case-combination (incoming
and matching names are canonicalized by lower-casing) should work.
Note that there is additional performance overhead since incoming
property names need to be lower-cased before comparison, for cases
where there are upper-case letters. Overhead for names that are
already lower-case should be negligible however.
Feature is disabled by default.
Since:
2.5
Just a quick update as I was looking for same answer and a code snippet objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
Since v 2.13 use builder:
XmlMapper xmlMapper = (XmlMapper) getObjectMapper();
private ObjectMapper getObjectMapper() {
return XmlMapper.builder()
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)
.build();
}

JsonMappingException: No suitable constructor found for type [simple type, class ]: can not instantiate from JSON object

I am getting the following error when trying to get a JSON request and process it:
org.codehaus.jackson.map.JsonMappingException: No suitable constructor found for type [simple type, class com.myweb.ApplesDO]: can not instantiate from JSON object (need to add/enable type information?)
Here is the JSON I am trying to send:
{
"applesDO" : [
{
"apple" : "Green Apple"
},
{
"apple" : "Red Apple"
}
]
}
In Controller, I have the following method signature:
#RequestMapping("showApples.do")
public String getApples(#RequestBody final AllApplesDO applesRequest){
// Method Code
}
AllApplesDO is a wrapper of ApplesDO :
public class AllApplesDO {
private List<ApplesDO> applesDO;
public List<ApplesDO> getApplesDO() {
return applesDO;
}
public void setApplesDO(List<ApplesDO> applesDO) {
this.applesDO = applesDO;
}
}
ApplesDO:
public class ApplesDO {
private String apple;
public String getApple() {
return apple;
}
public void setApple(String appl) {
this.apple = apple;
}
public ApplesDO(CustomType custom){
//constructor Code
}
}
I think that Jackson is unable to convert JSON into Java objects for subclasses. Please help with the configuration parameters for Jackson to convert JSON into Java Objects. I am using Spring Framework.
EDIT: Included the major bug that is causing this problem in the above sample class - Please look accepted answer for solution.
So, finally I realized what the problem is. It is not a Jackson configuration issue as I doubted.
Actually the problem was in ApplesDO Class:
public class ApplesDO {
private String apple;
public String getApple() {
return apple;
}
public void setApple(String apple) {
this.apple = apple;
}
public ApplesDO(CustomType custom) {
//constructor Code
}
}
There was a custom constructor defined for the class making it the default constructor. Introducing a dummy constructor has made the error to go away:
public class ApplesDO {
private String apple;
public String getApple() {
return apple;
}
public void setApple(String apple) {
this.apple = apple;
}
public ApplesDO(CustomType custom) {
//constructor Code
}
//Introducing the dummy constructor
public ApplesDO() {
}
}
This happens for these reasons:
your inner class should be defined as static
private static class Condition { //jackson specific
}
It might be that you got no default constructor in your class (UPDATE: This seems not to be the case)
private static class Condition {
private Long id;
public Condition() {
}
// Setters and Getters
}
It could be your Setters are not defined properly or are not visible (e.g. private setter)
I would like to add another solution to this that does not require a dummy constructor. Since dummy constructors are a bit messy and subsequently confusing. We can provide a safe constructor and by annotating the constructor arguments we allow jackson to determine the mapping between constructor parameter and field.
so the following will also work. Note the string inside the annotation must match the field name.
import com.fasterxml.jackson.annotation.JsonProperty;
public class ApplesDO {
private String apple;
public String getApple() {
return apple;
}
public void setApple(String apple) {
this.apple = apple;
}
public ApplesDO(CustomType custom){
//constructor Code
}
public ApplesDO(#JsonProperty("apple")String apple) {
}
}
When I ran into this problem, it was a result of trying to use an inner class to serve as the DO. Construction of the inner class (silently) required an instance of the enclosing class -- which wasn't available to Jackson.
In this case, moving the inner class to its own .java file fixed the problem.
Generally, this error comes because we don’t make default constructor.
But in my case:
The issue was coming only due to I have made used object class inside parent class.
This has wasted my whole day.
Thumb Rule: Add a default constructor for each class you used as a mapping class. You missed this and issue arise!
Simply add default constructor and it should work.
Can you please test this structure. If I remember correct you can use it this way:
{
"applesRequest": {
"applesDO": [
{
"apple": "Green Apple"
},
{
"apple": "Red Apple"
}
]
}
}
Second, please add default constructor to each class it also might help.
You have to create dummy empty constructor in our model class.So while mapping json, it set by setter method.
Regarding the last publication I had the same problem where using Lombok 1.18.* generated the problem.
My solution was to add #NoArgsConstructor (constructor without parameters), since #Data includes by default #RequiredArgsConstructor (Constructor with parameters).
lombok Documentation
https://projectlombok.org/features/all
That would solve the problem:
package example.counter;
import javax.validation.constraints.NotNull;
import lombok.Data;
#Data
#NoArgsConstructor
public class CounterRequest {
#NotNull
private final Integer int1;
#NotNull
private final Integer int2;
}
If you start annotating constructor, you must annotate all fields.
Notice, my Staff.name field is mapped to "ANOTHER_NAME" in JSON string.
String jsonInString="{\"ANOTHER_NAME\":\"John\",\"age\":\"17\"}";
ObjectMapper mapper = new ObjectMapper();
Staff obj = mapper.readValue(jsonInString, Staff.class);
// print to screen
public static class Staff {
public String name;
public Integer age;
public Staff() {
}
//#JsonCreator - don't need this
public Staff(#JsonProperty("ANOTHER_NAME") String n,#JsonProperty("age") Integer a) {
name=n;age=a;
}
}
You must realize what options Jackson has available for deserialization. In Java, method argument names are not present in the compiled code. That's why Jackson can't generally use constructors to create a well-defined object with everything already set.
So, if there is an empty constructor and there are also setters, it uses the empty constructor and setters. If there are no setters, some dark magic (reflections) is used to do it.
If you want to use a constructor with Jackson, you must use the annotations as mentioned by #PiersyP in his answer. You can also use a builder pattern. If you encounter some exceptions, good luck. Error handling in Jackson sucks big time, it's hard to understand that gibberish in error messages.
Add default constructors to all the entity classes
Failing custom jackson Serializers/Deserializers could also be the problem. Though it's not your case, it's worth mentioning.
I faced the same exception and that was the case.
For me, this used to work, but upgrading libraries caused this issue to appear. Problem was having a class like this:
package example.counter;
import javax.validation.constraints.NotNull;
import lombok.Data;
#Data
public class CounterRequest {
#NotNull
private final Integer int1;
#NotNull
private final Integer int2;
}
Using lombok:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.0</version>
</dependency>
Falling back to
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
Fixed the issue. Not sure why, but wanted to document it for future.

Categories

Resources