Jackson conflicting setters, even with #JsonIgnore and #JsonProperty - java

I'm at a complete loss here. I have a class with overloaded setters for a property, and for the life of me cannot get Jackson to pick a correct setter. Stripping out the things not needed from the class, here's the base of what I've got:
class TestDTO {
#Setter(onMethod_={#JsonProperty})
#JsonProperty
protected CustomWrapper wrappedValues = new CustomWrapper();
#JsonIgnore
#XmlTransient
public RecordAudit setWrappedValues(List<WrappedObject> wrappedValues) {
this.customWrapper = new CustomWrapper(wrappedValues);
return this;
}
#JsonIgnore
#XmlTransient
public RecordAudit setWrappedValues(CustomWrapper customWrapper) {
this.customWrapper = customWrapper;
return this;
}
}
I have tried every combination I can think of of #JsonIgnore and #JsonProperty. I've tried just adding #JsonProperty to the #Setter annotation, I've tried only adding #JsonIgnore to the two custom setters, I've tried only #JsonProperty on the field itself, but no matter what I try, I get the following error:
Conflicting setter definitions for property "wrappedValues": ...#setWrappedValues(1 params) vs ...#setWrappedValues(1 params)
Does anyone have any ideas what's going on here? Using Jackson 2.12.4, so I think just #JsonProperty should be all that's needed, but as I mentioned above, that still results in the same error.
This is on JDK 11 if that makes a difference, I'm still new to 11, so am not sure how much that affects this.

You need to mark setter you want to use as com.fasterxml.jackson.annotation.JsonSetter.
class TestDTO {
protected CustomWrapper wrappedValues = new CustomWrapper();
public RecordAudit setWrappedValues(List<WrappedObject> wrappedValues) {
this.customWrapper = new CustomWrapper(wrappedValues);
return this;
}
#JsonSetter
public RecordAudit setWrappedValues(CustomWrapper customWrapper) {
this.customWrapper = customWrapper;
return this;
}
}
P.S. Your #Setter aren't generating anything since there are methods with name setWrappedValues

Related

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.

Make #JsonAnySetter work with #Value Lombok

I have a json object with a lot of properties (~80 properties) I want to deserialize in a POJO without creating manually all the properties. I was able to do this by using the #JsonAnySetter with a Map property like described here.
Now I want to make this work by making my POJO immutable using Lombok.
I tried this but it does only deserialize the id and code properties. Any idea on how to make it work?
#Value
#Builder
#EqualsAndHashCode
#JsonDeserialize(builder = Product.ProductBuilder.class)
class Product {
#JsonProperty
private String id;
#JsonProperty
private String code;
#Getter(AccessLevel.NONE)
#Builder.Default
#JsonProperty
private Map<String, Optional<Object>> any = new HashMap<>();
#JsonAnyGetter
public Map<String, Optional<Object>> getAny(){
return this.any;
}
#JsonAnySetter
public void setAny(String key, Optional<Object> value){
this.any.put(key, value);
}
}
Update 2021-02-01: Lombok v1.18.16
Starting with v1.18.16, Lombok automatically copies #JsonAnySetter to the #Singular methods in builder. In combination with #Jacksonized you can simply use this code:
#Value
#Jacksonized
#Builder
class Product {
private String id;
private String code;
#JsonAnySetter
#Singular("any")
private Map<String, Object> any;
}
Older Lombok versions
For previous Lombok version, this requires some customization of the generated builder class.
Customizing a lombok builder can be done by simply adding its inner class header to your class. Lombok detects that there is already a builder class and just adds all the things that are not already present. This means you can add your own methods, and if those happen to have the same name than a method that lombok would generate, lombok skips this method.
With this approach, we replace the builder's setter method for "any", adding the required #JsonAnySetter to it. I use a LinkedHashMap as map in case the order is relevant; you can use a regular HashMap if it's not.
Furthermore, we replace the build() method to make sure the map you supply to the constructor is immutable. I use Guava's ImmutableMap here. This will make the created instance an immutable value.
#Value
#Builder
#JsonDeserialize(builder = Product.ProductBuilder.class)
class Product {
#JsonProperty
private String id;
#JsonProperty
private String code;
#Getter(onMethod_ = #JsonAnyGetter)
private Map<String, Object> any;
#JsonPOJOBuilder(withPrefix = "")
public static class ProductBuilder {
#JsonAnySetter
public ProductBuilder any(String anyKey, Object anyValue) {
if (this.any == null) {
this.any = new LinkedHashMap<String, Object>();
}
this.any.put(anyKey, anyValue);
return this;
}
public Product build() {
return new Product(id, code, any == null ? ImmutableMap.of() : ImmutableMap.copyOf(any));
}
}
}

How to override Lombok Setter methods

I'm using lombok in my project and generation Setters and Getters using #Setters and #Getters annotations on top of POJO class. I'm trying to override setters method of a property but it's not working
I want to check if JSON property is Empty or Null i want to set default value in Setter method
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
#Accessors(chain = true)
#ToString
public class DefaultModel {
private String name;
#Setter(AccessLevel.NONE)private String age;
public void setAge(String age) {
if(age==null||age.trim().isEmpty()||age.equals("null")) {
this.age="10";
}else {
this.age=age;
}
}
}
Working scenarios:
{
"name":"some",
"age":null
}
{
"name":"some",
"age":"null"
}
{
"name":"some",
"age":" "
}
Failed Scenario :
{
"name":"some"
}
Output:
DefaultModel(name=some, age=null)
And i'm following this as reference also here, but no luck so far
Either you just hit a bug I've never seen or you're testing it wrong.
An annotation like
#Setter(AccessLevel.NONE) private String age;
on the field level indeed stops the setter from being generated. But given that you're defining a setter, you don't even need it. An explicit #Setter stops the generation, too.
I've just tried your example using Eclipse 4.7.3a and Lombok 1.18.0 and your (buggy) setter gets called. I've been using Lombok a lot over a few years and never encountered such a bug.
Most probably the problem is that your JSON deserializer does not use setters at all. I guess, you're testing something like
DefaultModel defaultModel = deserialize("{\"name\":\"some\"}", DefaultModel.class);
instead of testing the setter directly. And that's the problem.
It possible that JSON deserializer uses constructor generated by Lombok (not setters).
Have a look here:
Jackson deserialize default values missing

Java - How to avoid creation of setter only for a particular class needs?

I am using Hibernate and currently using the setter to set the relation to parent in children at creation time (to avoid doing this manually for both sides). How I can avoid use of setter or avoid expose it to the rest of classes and get the same behaviour. Is it ok to use reflection? This is the code:
#Entity
#Table(name = "TEST_GROUP")
#Getter
public class TestGroupEntity extends AuditedEntity{
#ManyToOne
#JoinColumn(name = "owner", nullable = false)
protected UserEntity owner;
#Column(name = "description")
#Setter
protected String description;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
protected Set<TestEntity> tests = Sets.newHashSet();
public boolean addTest(TestEntity testEntity) {
return tests.add(testEntity);
}
public boolean removeTest(TestEntity testEntity) {
return tests.remove(testEntity);
}
public TestGroupEntity(UserEntity owner, Set<TestEntity> tests) {
this.owner = owner;
owner.setTestGroupEntity(this); ! how to avoid creation of setter
this.tests = tests;
tests.stream().forEach(t -> t.setTestGroupEntity(this)); ! how to avoid creation of setter
}
}
This is the children class ( I would like to keep immutability on api level):
#MappedSuperclass
#AllArgsConstructor
public class TestEntity extends AuditedEntity {
#Column(name = "name", nullable = false)
protected String name;
#Column(name = "description")
protected String description;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "test_group", nullable = false)
protected TestGroupEntity testGroupEntity;
public void setTestGroupEntity(TestGroupEntity testGroupEntity) {
this.testGroupEntity = testGroupEntity;
}
}
Edit: I think commented sections of code was not visible. Sorry.
How I can avoid use of setter or avoid expose it to the rest of
classes and get the same behaviour. Is it ok to use reflection?
Of course you can for example reduce visibility of public setters to a visibility less wide than public in order that client classes of your entities cannot use them.
Which is in your case the real problem since accessing any data from inside the object is possible in anyway
From hibernate doc :
Attributes (whether fields or getters/setters) need not be declared
public. Hibernate can deal with attributes declared with public,
protected, package or private visibility. Again, if wanting to use
runtime proxy generation for lazy loading the visibility for the
getter/setter should be at least package visibility.
So, try to use private setter for desired field. It should address your problem.
Update After comment
You have several workarounds to address your problem :
using reflection (your basic idea).
Drawback : it brings a little complexity, not a full check at compile-time and at last, someone who sees your code could wonder why you used that...
It is the same thing for any concepts which relies on reflection such as AOP.
declaring these setters with package-private level and put the 3 classes in the same package. Drawback : the used package.
creating public init methods which raises an exception if it used more than once for a same object. In this way, you guarantee the coherence of the object if bad used. Drawback : method which should not be used by clients is still provided to clients.
Unfortunately, you have not a perfect solution since Java visibility mechanisms cannot provide a ready-to-use solution for what you are looking for.
Personally, I prefer reflection or init method solutions.
Personally, I noticed that in based-class languages as Java, a good developer has often the reflex to over- protect accessibility of objects/methods. In fact, in many cases, it is not needed because it will not break the application or data integrity.
Here an example with init method :
public TestGroupEntity(UserEntity owner, Set<TestEntity> tests) {
this.owner = owner;
owner.constructInit(this);
this.tests = tests;
tests.stream().forEach(t -> t.constructInit(this));
}
public class UserEntity {
private TestGroupEntity testGroupEntity;
public void constructInit(TestGroupEntity testGroupEntity) {
if (this.testGroupEntity != null) {
throw new IllegalArgumentException("forbidden");
}
this.testGroupEntity=testGroupEntity;
}
}
Make a constructor in your parent class and call it from child.
Here is the parent constructor looks like
public AuditedEntity(UserEntity owner, Set<TestEntity> tests){
this.owner = owner;
this.tests = tests;
}
And change your child constructor like
public TestGroupEntity(UserEntity owner, Set<TestEntity> tests) {
super(owner,tests);
}

Want to hide some fields of an object that are being mapped to JSON by Jackson

I have a User class that I want to map to JSON using Jackson.
public class User {
private String name;
private int age;
private int securityCode;
// getters and setters
}
I map this to a JSON string using -
User user = getUserFromDatabase();
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(user);
I don't want to map the securityCode variable. Is there any way of configuring the mapper so that it ignores this field?
I know I can write custom data mappers or use the Streaming API but I would like to know if it possible to do it through configuration?
You have two options:
Jackson works on setters-getters of fields. So, you can just remove getter of field which you want to omit in JSON. ( If you don't need getter at other place.)
Or, you can use the #JsonIgnore annotation of Jackson on getter method of that field and you see there in no such key-value pair in resulted JSON.
#JsonIgnore
public int getSecurityCode(){
return securityCode;
}
Adding this here because somebody else may search this again in future, like me. This Answer is an extension to the Accepted Answer
You have two options:
1. Jackson works on setters-getters of fields. So, you can just remove getter of field which you want to omit in JSON. ( If you don't need getter at other place.)
2. Or, you can use the `#JsonIgnore` [annotation of Jackson][1] on getter method of that field and you see there in no such key-value pair in resulted JSON.
#JsonIgnore
public int getSecurityCode(){
return securityCode;
}
Actually, newer version of Jackson added READ_ONLY and WRITE_ONLY annotation arguments for JsonProperty. So you could also do something like this.
#JsonProperty(access = Access.WRITE_ONLY)
private String securityCode;
instead of
#JsonIgnore
public int getSecurityCode(){
return securityCode;
}
you also can gather all properties on an annotation class
#JsonIgnoreProperties( { "applications" })
public MyClass ...
String applications;
If you don't want to put annotations on your Pojos you can also use Genson.
Here is how you can exclude a field with it without any annotations (you can also use annotations if you want, but you have the choice).
Genson genson = new Genson.Builder().exclude("securityCode", User.class).create();
// and then
String json = genson.serialize(user);
Field Level:
public class User {
private String name;
private int age;
#JsonIgnore
private int securityCode;
// getters and setters
}
Class Level:
#JsonIgnoreProperties(value = { "securityCode" })
public class User {
private String name;
private int age;
private int securityCode;
}
if you are using GSON you have to mark the field/member declarations as #Expose and use the GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()
Don't forget to mark your sub classes with #Expose otherwise the fields won't show.
I suggest you use this.
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private int securityCode;
This allows you to set the value of securityCode(especially if you use lombok #Setter) and also prevent the field from showing up in the GET request.
I had a similar case where I needed some property to be deserialized (JSON to Object) but not serialized (Object to JSON)
First i went for #JsonIgnore - it did prevent serialization of unwanted property, but failed to de-serialize it too. Trying value attribute didn't help either as it requires some condition.
Finally, working #JsonProperty with access attribute worked like a charm.

Categories

Resources