diff --git a/CHANGELOG.md b/CHANGELOG.md index 147538e9f3a..eece9d05cde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### 6.8-SNAPSHOT #### Bugs +* Fix #5214: null values are omitted by default, which means custom resources by in large won't need JsonIncludes annotations. The only time that is required is when null is to be preserved via @JsonInclude(value = Include.ALWAYS) on the relevant field. * Fix #5218: No export for `io.fabric8.tekton.triggers.internal.knative.pkg.apis.duck.v1beta1` in tekton v1beta1 triggers model * Fix #5224: Ensuring jetty sets the User-Agent header * Fix #4225: Enum fields written in generated crd yaml diff --git a/doc/CHEATSHEET.md b/doc/CHEATSHEET.md index 4d2c0b1f3c2..b869c018d16 100644 --- a/doc/CHEATSHEET.md +++ b/doc/CHEATSHEET.md @@ -1859,6 +1859,10 @@ import io.fabric8.kubernetes.model.annotation.Version; public class CronTab extends CustomResource implements Namespaced { } ``` + +**Note:** Null values in your custom resource will be omitted by default by the client or when directly using KubernetesSerialization, or the static Serialization methods. If you have a situation where a null value +must be present in the serialized form, then mark the field with @JsonInclude(value = Include.ALWAYS). + You can find other helper classes related to `CronTab` in our [tests](https://github.com/fabric8io/kubernetes-client/tree/master/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/crd). For now, we can proceed with it's common usage examples: - Get Instance of client for our `CustomResource`: diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java index e0b20aba3a9..dee1e2d5bdf 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java @@ -16,6 +16,8 @@ package io.fabric8.kubernetes.client.utils; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationConfig; @@ -89,6 +91,8 @@ public KubernetesSerialization(ObjectMapper mapper, boolean searchClassloaders) protected void configureMapper(ObjectMapper mapper) { mapper.registerModules(new JavaTimeModule(), unmatchedFieldTypeModule); mapper.disable(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE); + // omit null fields, but keep null map values + mapper.setDefaultPropertyInclusion(JsonInclude.Value.construct(Include.NON_NULL, Include.ALWAYS)); HandlerInstantiator instanciator = mapper.getDeserializationConfig().getHandlerInstantiator(); mapper.setConfig(mapper.getDeserializationConfig().with(new HandlerInstantiator() { diff --git a/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/utils/SerializationTest.java b/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/utils/SerializationTest.java index 01d4fd4e0ad..78079e5c05e 100644 --- a/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/utils/SerializationTest.java +++ b/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/utils/SerializationTest.java @@ -16,6 +16,7 @@ package io.fabric8.kubernetes.client.utils; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonUnwrapped; @@ -77,6 +78,27 @@ class SerializationTest { + static class WithNull { + @JsonInclude(value = Include.ALWAYS) + public Integer field; + } + + static class WithoutNull { + public Integer field; + } + + @Test + void testNullSerialization() throws Exception { + assertEquals("---\nfield: null\n", Serialization.asYaml(new WithNull())); + assertEquals("{\"field\":null}", Serialization.asJson(new WithNull())); + assertEquals("--- {}\n", Serialization.asYaml(new WithoutNull())); + assertEquals("{}", Serialization.asJson(new WithoutNull())); + // map null values should be preserved + Map map = new HashMap<>(); + map.put("key", null); + assertEquals("{\"key\":null}", Serialization.asJson(map)); + } + @Test void unmarshalCRDWithSchema() throws Exception { // Given diff --git a/kubernetes-client/src/test/java/io/fabric8/kubernetes/client/internal/PatchUtilsTest.java b/kubernetes-client/src/test/java/io/fabric8/kubernetes/client/internal/PatchUtilsTest.java index e022a76112e..cf21c474fbf 100644 --- a/kubernetes-client/src/test/java/io/fabric8/kubernetes/client/internal/PatchUtilsTest.java +++ b/kubernetes-client/src/test/java/io/fabric8/kubernetes/client/internal/PatchUtilsTest.java @@ -57,4 +57,16 @@ void testDiff() { PatchUtils.jsonDiff(rc1, rc2, false, new KubernetesSerialization())); } + @Test + void testDiffRemove() { + ReplicationController rc1 = new ReplicationControllerBuilder().withNewStatus().withFullyLabeledReplicas(1).endStatus() + .withNewMetadata().withName("x").endMetadata().build(); + + ReplicationController rc2 = new ReplicationControllerBuilder(rc1).withStatus(null).build(); + + assertEquals( + "[{\"op\":\"remove\",\"path\":\"/status\"}]", + PatchUtils.jsonDiff(rc1, rc2, false, new KubernetesSerialization())); + } + }