-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Global @JsonInclude(Include.NON_NULL)
for all properties with a specific type
#1522
Comments
Annotation introspector does not have any annotation-specific behavior: any and all are applied without knowledge of intended semantics. |
Ok. I think it is still possible there is a subtle bug that you are seeing, although it is not in mix-in handling at all. Rather it may be in logic of determining details of Could you simplify reproduction with minimal case: does not need to be |
Okay, here we go. Might not be a minimal case, but it's simulating the public class AnnotationIntrospectorSampleTest {
public static class MyBean {
public Integer one;
public NullableInteger two;
public NullableInteger three;
public NullableInteger four;
}
public static class NullableInteger {
public Integer value;
public NullableInteger(Integer value) {
this.value = value;
}
}
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class NullableIntegerMixIn {
}
private ObjectMapper initObjectMapper(JacksonAnnotationIntrospector workaround) {
SimpleModule optionalBeanModule = new SimpleModule();
// custom serializer to simulate behavior of Java-8 Optional<>
optionalBeanModule.addSerializer(NullableInteger.class, new JsonSerializer<NullableInteger>() {
@Override
public void serialize(NullableInteger nullable, JsonGenerator gen, SerializerProvider prov) throws IOException {
if (nullable.value == null) {
gen.writeNull();
} else {
gen.writeNumber(nullable.value);
}
}
});
// custom deserializer to simulate behavior of Java-8 Optional<>
optionalBeanModule.addDeserializer(NullableInteger.class, new JsonDeserializer<NullableInteger>() {
@Override
public NullableInteger deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
Integer value = parser.getCodec().readValue(parser, Integer.class);
return new NullableInteger(value);
}
@Override
public NullableInteger getNullValue(DeserializationContext ctxt) throws JsonMappingException {
return new NullableInteger(null);
}
});
ObjectMapper mapper = new ObjectMapper();
// default: include everything (even NULL values)
mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
// register specific serializer/deserializer to mock Java-8 Optional<> behavior
mapper.registerModule(optionalBeanModule);
// add the mix-in with the non-default JsonInclude.Include.NON_NULL annotation
mapper.addMixIn(NullableInteger.class, NullableIntegerMixIn.class);
if (workaround != null) {
mapper.setAnnotationIntrospector(workaround);
}
return mapper;
}
@Test
public void testDeserialization() throws JsonParseException, JsonMappingException, IOException {
String json = "{\"one\":null,\"two\":2,\"three\":null}";
MyBean bean = initObjectMapper(null).readValue(json, MyBean.class);
Assert.assertNull(bean.one);
Assert.assertNotNull(bean.two);
Assert.assertEquals(Integer.valueOf(2), bean.two.value);
Assert.assertNotNull(bean.three);
Assert.assertNull(bean.three.value);
Assert.assertNull(bean.four);
}
@Test
public void testSerialization() throws JsonProcessingException {
MyBean bean = new MyBean();
bean.one = null;
bean.two = new NullableInteger(2);
bean.three = new NullableInteger(null);
bean.four = null;
String result = initObjectMapper(null).writeValueAsString(bean);
String expected = "{\"one\":null,\"two\":2,\"three\":null}";
// FAILS, due to result = "{one:null,two:2,three:null,four:null}"
Assert.assertEquals(expected, result);
}
@Test
public void testSerializationWorkaround() throws JsonProcessingException {
MyBean bean = new MyBean();
bean.one = null;
bean.two = new NullableInteger(2);
bean.three = new NullableInteger(null);
bean.four = null;
String result = initObjectMapper(new JacksonAnnotationIntrospector() {
@Override
public JsonInclude.Value findPropertyInclusion(Annotated a) {
if (NullableInteger.class.equals(a.getRawType())) {
return JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL);
}
return super.findPropertyInclusion(a);
}
}).writeValueAsString(bean);
String expected = "{\"one\":null,\"two\":2,\"three\":null}";
// SUCCESS (thanks to workaround)
Assert.assertEquals(expected, result);
}
} |
@CarstenWickner Thank you! While I can't guarantee quick resolution here (lots to work on right now), having a reproduction puts this close to head of my queue here, help much appreciated. |
@CarstenWickner Ok so I can reproduce the issue. But it seems to me that failure would result even by normal application of |
Since I'm using the actual Java In the above example, the following would do the trick as well: public static class MyBean {
public Integer one;
@JsonInclude(JsonInclude.Include.NON_NULL)
public NullableInteger two;
@JsonInclude(JsonInclude.Include.NON_NULL)
public NullableInteger three;
@JsonInclude(JsonInclude.Include.NON_NULL)
public NullableInteger four;
} So the |
@CarstenWickner Yes, but with reproduction you can easily add I did also observe that property-annotation does indeed work. |
@JsonInclude(Include.NON_NULL)
does not work as class annotation
How would one differentiate between those two use cases now though? I'm assuming one would have to provide an additional (optional) parameter on |
@CarstenWickner Ah. At first I thought you might not have understood the issue... and then going back to working, realized you did more than I did. :) Yes: there are 2 possible interpretations for what a
Currently code does (1), I think. But I'll spend bit more time to try to recall if I had some ideas of how to do (2). |
Ok. I suspect this usage is, after all, unsupported. While class-annotation is used as the default for properties contained, it is not used as the default of all properties of annotated type.
which is an alternative for mix-ins for However it might be possible to add desired functionality via |
In a nutshell,
For my particular use case, both solutions are acceptable. In general, option (2) would add more value. |
@CarstenWickner I don't think we still agree on mix-in part. What I am saying is that effect of @JsonInclude(Include.NON_NULL
class MyType {
} is identical to class MyType {
}
@JsonInclude(Include.NON_NULL
class MixIn {
}
mapper.addMixIn(MyType.class, MixIn.class); so from Jackson perspective. It's just definition of what class annotation (direct or indirect via mix-in) means. But I understand that from user perspective application matters so you would need it to apply using mix-ins. With that settled, yes, change of semantics would be somewhat far reaching. Existing interpretation was meant to support convenience of suppressing "all nulls" of values of specific POJOs. So like: @JsonInclude(Include.NON_NULL
public class CompactPOJO {
public String nameMaybe; // only serialized if not null
public Map<String,String> props; // same
// and so on
} But what you are looking for, equally useful, would be to specify that any and all properties value What could be possible, however, could be adding something like: // hypothetical, does not exist yet:
mapper.configOverride(NullableInteger.class)
.setInclusionAsProperty(Include.NON_NULL) which would then acts as override between POJO-inclusion-default (existing class annotation), and property overrides. Or, possibly, as default to be overridden by POJO-inclusion-default... this is bit tricky. Does this make more sense? |
Regarding the override behaviour, I'd prefer this override to overrule the POJO-inclusion-default (at least for my particular use case). For now, one could keep it simple for this new feature and make the value provided via |
@JsonInclude(Include.NON_NULL)
does not work as class annotation@JsonInclude(Include.NON_NULL)
for all properties with a specific type
@CarstenWickner it seems to me that your preference would be good default preference, and for simplicity we could start with it. |
@cowtowncoder, I created PR #1564 to illustrate what I imagine this to look like. Maybe you can have a look. |
PR #1566 is basically the same proof-of-concept, but based on the current |
Thanks! Yes, it should be based for |
Included now for next 2.9.0 version: either 2.9.0.pr4, or, if no more pre-releases made, 2.9.0 itself. |
When trying to optionally skip the certain fields when serialising certain beans, I put them into Java-8
Optional<>
wrappers, as that's what allows me to differentiate between omitted and explicitnull
values on deserialisation.As described in my respective Stackoverflow question and answer, I struggled to achieve this in a generic way via
MixIns
, as the (default)JacksonAnnotationIntrospector
is ignoring them (at least for the@JsonInclude
annotation I was looking at).My final workaround is far from ideal, but at least works for this one isolated use case.
Are
MixIns
intentionally left out there or is it a bug?Confirmed this under 2.7.4, 2.7.8, 2.8.5, and 2.8.6.
The text was updated successfully, but these errors were encountered: