56

I have a simple interface with getter and setter for a property.

public interface HasMoney { 

      Money getMoney();

      void setMoney(Money money);

 }

I have another class UserAccount which implements this interface.

public class UserAccount implements HasMoney {

       private Money money;

       @Override
       Money getMoney() // fill in the blanks

       @Override
       void setMoney(Money money) // fill in the blanks

}

My problem is that I want to serialize the money property but ignore while deserializing it i.e., dont accept any values from the user for this property. I have tried @JsonIgnore on setter and @JsonIgnore(false) on the getter, it does ignore it but it does so while serializing it also.

I tried @JsonIgnore on the setter and @JsonProperty on the getter just to explicitly tell Jackson that we intend to track this property, that seems to crash the application when money property is sent to the server and Jackson tries to deserialize it throwing up MalformedJsonException : cannot construct object of type Money.

The most wierd thing is that putting @JsonIgnore on the setter and @JsonProperty on the setter works for most cases when the property is primitive.

Perception
  • 79,279
  • 19
  • 185
  • 195
Nishant Nagwani
  • 1,160
  • 3
  • 13
  • 26

5 Answers5

73

Version 2.6.0+ allows this to be done with @JsonIgnoreProperties at the class level.

@JsonIgnoreProperties(value={ "money" }, allowGetters=true)

Take a look at this closed issue: https://github.com/FasterXML/jackson-databind/issues/95

kmek
  • 1,166
  • 1
  • 11
  • 14
  • 3
    This solution is cleaner – Lucas Kuhlemann Sep 24 '16 at 21:07
  • This seems to be the most efficient solution, thank you! – Alexander Eliseyev Mar 02 '19 at 10:12
  • 2
    This also works with immutable kotlin data classes. – HeikoG Mar 28 '19 at 17:29
  • 1
    This was the only working solution, when using Lombok, because then I don't have code for getters/setter to put annotations on (like accepted answer). – Somnium Jul 25 '19 at 20:19
  • 1
    @Somnium I'm glad you liked my answer. As a note, if you define your own getters/setters, Lombok won't generate its own. This allows you to write getters/setters for certain fields, decorate these methods with annotations, then let Lombok generate the other getters/setters. – kmek Jul 26 '19 at 00:18
  • At class level you can only do one value. Therefore, if you want to have multiple values ignored, in one classes properties, you have to put one such line on each property to ignore. – Beezer Oct 12 '20 at 11:27
  • @Beezer The class level annotation accepts multiple values. They need to be comma separated, like so @JsonIgnoreProperties(value={ "money", "year" }, allowGetters=true) – kmek Oct 13 '20 at 07:09
  • I tried that, no dice. Thanks all the same. I am using Jackson version 2.10.2...soooo...:=( – Beezer Oct 13 '20 at 16:03
  • I'm on the same version (Jackson core 2.10.2 with the latest spring boot) and it worked for me. You should see the values always deserialized as null. I'll try to create an example project to show you what I'm seeing – kmek Oct 13 '20 at 18:59
  • @Beezer take a look at https://github.com/kmek91/jacksonDeserializationDemo I've included two kinds of tests. One where I create an ObjectMapper and invoke readValue manually (ManualTests.java) and one where I start the Spring Boot app and send a request to the Controller to see what Spring deserializes the payload as (ControllerTests). All tests pass and ignored fields are ignored. Hope this helps! – kmek Oct 14 '20 at 05:16
  • 1
    Many thanks...I will give it another shot...@kmek :=) – Beezer Oct 16 '20 at 14:50
  • This is also correct solution for scala case class – Jorge Jan 27 '22 at 01:18
33

Ok, so the behavior of @JsonIgnore was radically changed from 1.9 onwards (for the worse imo). Without going into the devilish details of why your property is not being ignore during deserialization, try this code to fix it:

public class UserAccount implements HasMoney {
    @JsonIgnore
    private BigDecimal money;

    // Other variable declarations, constructors

    @Override
    @JsonProperty
    public BigDecimal getMoney() {
        return money;
    }

    @JsonIgnore
    @Override
    public void setMoney(final BigDecimal money) {
        this.money = money;
    }

    // Other getters/setters
}

Note the use of @JsonIgnore on the field - its required for a working solution.

Note: depending on your environment and use case, you may need additional configuration on your ObjectMapper instance, for example, USE_GETTERS_AS_SETTERS, AUTO_DETECT_GETTERS, AUTO_DETECT_SETTERS etc.

leventov
  • 14,760
  • 11
  • 69
  • 98
Perception
  • 79,279
  • 19
  • 185
  • 195
  • 1
    This is actually part of the solution. I also had to add objectMapper.disable(MapperFeature.USE_GETTERS_AS_SETTERS) and it worked then. Please edit your answer and I will accept it. Its sad that there is no cleaner way to this (Atleast I was not able to find it ). – Nishant Nagwani Apr 16 '13 at 19:14
  • @NishantNagwani - odd, works for me without that feature enabled. I'm adding an optional note to my answer but I'm curious if something is different about your setup that would necessitate `USE_GETTERS_AS_SETTERS`. – Perception Apr 16 '13 at 19:28
  • I tried it again. Doesnt work with my environment. I am using jackson 2.1.1 . Here is the error I get { "message": "Malformed json passed to server, incorrect data type used: \nProblem deserializing 'setterless' property 'money': get method returned null (through reference chain: com.abc.def.Money])" } – Nishant Nagwani Apr 16 '13 at 20:50
  • Please join me in [chat](http://chat.stackoverflow.com/rooms/28334/ignoring-property-when-deserializing). – Perception Apr 16 '13 at 20:58
10

With Jackson 2.10 you can implement a read-only field like this:

A real field

public class UserAccount implements HasMoney {

   @JsonProperty(access = JsonProperty.Access.READ_ONLY)
   private Money money;
   
   // getter and setter

}

A virtual field

@JsonIgnoreProperties(ignoreUnknown = true) // to ignore ALL unknown properties
// OR
@JsonIgnoreProperties(value = {"money"}, allowGetters = true) // to ignore only 'money' input
public class UserAccount implements HasMoney {

   @JsonProperty
   public Money getMoney() {
       // some calculation
   }

}

The value will be serialized but ignored during deserialization.

Sergey Nemchinov
  • 1,348
  • 15
  • 21
  • How is this different than my answer? The key is JsonIgnoreProperties which was updated in version 2.6.0 to work as detailed above. I don't think the JsonProperty with READ_ONLY access does anything for this problem. – kmek Oct 13 '20 at 07:12
  • If the field has a default value it will not be changed during deserialization. Even if JSON contains a new value for it. The annotation JsonProperty with READ_ONLY tells deserializer to keep the old value of the property during deserialization. – Sergey Nemchinov Oct 14 '20 at 10:21
6

In a case of you don't own or can't change the class by adding @JsonIgnore annotation, you would get expected result by using mixin starting from version 2.5 in your implementation.

public abstract class HasMoneyMixin {
    @JsonIgnore
    public abstract Money getMoney();
}

configure mapper to use mixin,

ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(HasMoney.class, HasMoneyMixin.class);
// avoid failing if all properties are empty
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
Ruwanka De Silva
  • 3,555
  • 6
  • 35
  • 51
5

Apparently my solution is late, but definitely will be helpful for others.

Prehistory: in my project there is a class reading a JSON string directly into an entity. The JSON contains a property which is not a variable of the class. Because of

objectMapper.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

an instance of the entity will not be created during deserialization (an exception in such a case is desired in our project).

Solution:

objectMapper.addHandler(new DeserializationProblemHandler() {
    @Override
    public boolean handleUnknownProperty(DeserializationContext ctxt, JsonParser p, JsonDeserializer<?> deserializer, Object beanOrClass, String propertyName) throws IOException {
       if( (propertyName.equals("propertyToBeIgnored") && beanOrClass.getClass().equals(ClassOfTheProperty.class)) {
           p.skipChildren();
           return true;
       } else {
           return false;
       }
    }
});
tyoma17
  • 81
  • 3
  • 7