diff --git a/.github/workflows/maven_release.yml b/.github/workflows/maven_release.yml index 7efe6ca..cfb2dc3 100644 --- a/.github/workflows/maven_release.yml +++ b/.github/workflows/maven_release.yml @@ -15,10 +15,10 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: - java-version: 11 + java-version: 17 distribution: 'temurin' - name: Cache and restore Maven packages on master uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 @@ -59,7 +59,7 @@ jobs: - name: Set up Maven Central Repository uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: - java-version: 11 + java-version: 17 distribution: 'temurin' server-id: central server-username: MAVEN_USERNAME diff --git a/.github/workflows/maven_test.yml b/.github/workflows/maven_test.yml index 15894f3..d3b05c2 100644 --- a/.github/workflows/maven_test.yml +++ b/.github/workflows/maven_test.yml @@ -17,10 +17,10 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: - java-version: 11 + java-version: 17 distribution: 'temurin' - name: Cache and restore Maven packages on master uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 diff --git a/pom.xml b/pom.xml index 4e2c3a7..b1164cb 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,8 @@ 7.0.5.Final - 2.21.0 + 2.21.0 + 3.0.4 2.0.1.Final 3.1.1 5.14.2 @@ -60,10 +61,17 @@ com.fasterxml.jackson jackson-bom - ${jackson-bom.version} + ${jackson2-bom.version} import pom + + tools.jackson + jackson-bom + ${jackson3-bom.version} + pom + import + org.junit junit-bom @@ -78,6 +86,12 @@ com.fasterxml.jackson.core jackson-databind + provided + + + tools.jackson.core + jackson-databind + provided javax.validation @@ -166,6 +180,20 @@ true + + compile-java-17 + compile + + compile + + + 17 + + ${project.basedir}/src/main/java17 + + true + + @@ -233,7 +261,7 @@ - ${java.version} + 17 none false false diff --git a/src/main/java/org/openapitools/jackson/nullable/JsonNullableBeanPropertyWriter.java b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2BeanPropertyWriter.java similarity index 69% rename from src/main/java/org/openapitools/jackson/nullable/JsonNullableBeanPropertyWriter.java rename to src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2BeanPropertyWriter.java index 1f8fab6..61f8c1f 100644 --- a/src/main/java/org/openapitools/jackson/nullable/JsonNullableBeanPropertyWriter.java +++ b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2BeanPropertyWriter.java @@ -6,26 +6,26 @@ import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; import com.fasterxml.jackson.databind.util.NameTransformer; -public class JsonNullableBeanPropertyWriter extends BeanPropertyWriter +public class JsonNullableJackson2BeanPropertyWriter extends BeanPropertyWriter { private static final long serialVersionUID = 1L; - protected JsonNullableBeanPropertyWriter(BeanPropertyWriter base) { + protected JsonNullableJackson2BeanPropertyWriter(BeanPropertyWriter base) { super(base); } - protected JsonNullableBeanPropertyWriter(BeanPropertyWriter base, PropertyName newName) { + protected JsonNullableJackson2BeanPropertyWriter(BeanPropertyWriter base, PropertyName newName) { super(base, newName); } @Override protected BeanPropertyWriter _new(PropertyName newName) { - return new JsonNullableBeanPropertyWriter(this, newName); + return new JsonNullableJackson2BeanPropertyWriter(this, newName); } @Override public BeanPropertyWriter unwrappingWriter(NameTransformer unwrapper) { - return new UnwrappingJsonNullableBeanPropertyWriter(this, unwrapper); + return new UnwrappingJsonNullableJackson2BeanPropertyWriter(this, unwrapper); } @Override diff --git a/src/main/java/org/openapitools/jackson/nullable/JsonNullableBeanSerializerModifier.java b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2BeanSerializerModifier.java similarity index 83% rename from src/main/java/org/openapitools/jackson/nullable/JsonNullableBeanSerializerModifier.java rename to src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2BeanSerializerModifier.java index 75f4a28..6b755c8 100644 --- a/src/main/java/org/openapitools/jackson/nullable/JsonNullableBeanSerializerModifier.java +++ b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2BeanSerializerModifier.java @@ -8,7 +8,7 @@ import java.util.List; -public class JsonNullableBeanSerializerModifier extends BeanSerializerModifier +public class JsonNullableJackson2BeanSerializerModifier extends BeanSerializerModifier { @Override public List changeProperties(SerializationConfig config, @@ -19,7 +19,7 @@ public List changeProperties(SerializationConfig config, final BeanPropertyWriter writer = beanProperties.get(i); JavaType type = writer.getType(); if (type.isTypeOrSubTypeOf(JsonNullable.class)) { - beanProperties.set(i, new JsonNullableBeanPropertyWriter(writer)); + beanProperties.set(i, new JsonNullableJackson2BeanPropertyWriter(writer)); } } return beanProperties; diff --git a/src/main/java/org/openapitools/jackson/nullable/JsonNullableDeserializer.java b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2Deserializer.java similarity index 85% rename from src/main/java/org/openapitools/jackson/nullable/JsonNullableDeserializer.java rename to src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2Deserializer.java index 9ebee24..01eb04a 100644 --- a/src/main/java/org/openapitools/jackson/nullable/JsonNullableDeserializer.java +++ b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2Deserializer.java @@ -14,7 +14,7 @@ import java.io.IOException; -public class JsonNullableDeserializer extends ReferenceTypeDeserializer> { +public class JsonNullableJackson2Deserializer extends ReferenceTypeDeserializer> { private static final long serialVersionUID = 1L; @@ -25,8 +25,8 @@ public class JsonNullableDeserializer extends ReferenceTypeDeserializer deser) { + public JsonNullableJackson2Deserializer(JavaType fullType, ValueInstantiator inst, + TypeDeserializer typeDeser, JsonDeserializer deser) { super(fullType, inst, typeDeser, deser); if (fullType instanceof ReferenceType && ((ReferenceType) fullType).getReferencedType() != null) { this.isStringDeserializer = ((ReferenceType) fullType).getReferencedType().isTypeOrSubTypeOf(String.class); @@ -52,8 +52,8 @@ public JsonNullable deserialize(JsonParser p, DeserializationContext ctx } @Override - public JsonNullableDeserializer withResolved(TypeDeserializer typeDeser, JsonDeserializer valueDeser) { - return new JsonNullableDeserializer(_fullType, _valueInstantiator, + public JsonNullableJackson2Deserializer withResolved(TypeDeserializer typeDeser, JsonDeserializer valueDeser) { + return new JsonNullableJackson2Deserializer(_fullType, _valueInstantiator, typeDeser, valueDeser); } diff --git a/src/main/java/org/openapitools/jackson/nullable/JsonNullableDeserializers.java b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2Deserializers.java similarity index 82% rename from src/main/java/org/openapitools/jackson/nullable/JsonNullableDeserializers.java rename to src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2Deserializers.java index 04764e9..54a5d3b 100644 --- a/src/main/java/org/openapitools/jackson/nullable/JsonNullableDeserializers.java +++ b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2Deserializers.java @@ -7,12 +7,12 @@ import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; import com.fasterxml.jackson.databind.type.ReferenceType; -public class JsonNullableDeserializers extends Deserializers.Base { +public class JsonNullableJackson2Deserializers extends Deserializers.Base { @Override public JsonDeserializer findReferenceDeserializer(ReferenceType refType, DeserializationConfig config, BeanDescription beanDesc, TypeDeserializer contentTypeDeserializer, JsonDeserializer contentDeserializer) { - return (refType.hasRawClass(JsonNullable.class)) ? new JsonNullableDeserializer(refType, null, contentTypeDeserializer,contentDeserializer) : null; + return (refType.hasRawClass(JsonNullable.class)) ? new JsonNullableJackson2Deserializer(refType, null, contentTypeDeserializer,contentDeserializer) : null; } } diff --git a/src/main/java/org/openapitools/jackson/nullable/JsonNullableSerializer.java b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2Serializer.java similarity index 73% rename from src/main/java/org/openapitools/jackson/nullable/JsonNullableSerializer.java rename to src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2Serializer.java index b2957ae..038589e 100644 --- a/src/main/java/org/openapitools/jackson/nullable/JsonNullableSerializer.java +++ b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2Serializer.java @@ -7,7 +7,7 @@ import com.fasterxml.jackson.databind.type.ReferenceType; import com.fasterxml.jackson.databind.util.NameTransformer; -public class JsonNullableSerializer extends ReferenceTypeSerializer> { +public class JsonNullableJackson2Serializer extends ReferenceTypeSerializer> { private static final long serialVersionUID = 1L; @@ -17,14 +17,14 @@ public class JsonNullableSerializer extends ReferenceTypeSerializer ser) { + protected JsonNullableJackson2Serializer(ReferenceType fullType, boolean staticTyping, + TypeSerializer vts, JsonSerializer ser) { super(fullType, staticTyping, vts, ser); } - protected JsonNullableSerializer(JsonNullableSerializer base, BeanProperty property, - TypeSerializer vts, JsonSerializer valueSer, NameTransformer unwrapper, - Object suppressableValue) + protected JsonNullableJackson2Serializer(JsonNullableJackson2Serializer base, BeanProperty property, + TypeSerializer vts, JsonSerializer valueSer, NameTransformer unwrapper, + Object suppressableValue) { // Keep suppressNulls to false to always serialize JsonNullable[null] super(base, property, vts, valueSer, unwrapper, @@ -36,7 +36,7 @@ protected ReferenceTypeSerializer> withResolved(BeanProperty pro TypeSerializer vts, JsonSerializer valueSer, NameTransformer unwrapper) { - return new JsonNullableSerializer(this, prop, vts, valueSer, unwrapper, + return new JsonNullableJackson2Serializer(this, prop, vts, valueSer, unwrapper, _suppressableValue); } @@ -44,7 +44,7 @@ protected ReferenceTypeSerializer> withResolved(BeanProperty pro public ReferenceTypeSerializer> withContentInclusion(Object suppressableValue, boolean suppressNulls) { - return new JsonNullableSerializer(this, _property, _valueTypeSerializer, + return new JsonNullableJackson2Serializer(this, _property, _valueTypeSerializer, _valueSerializer, _unwrapper, suppressableValue); } diff --git a/src/main/java/org/openapitools/jackson/nullable/JsonNullableSerializers.java b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2Serializers.java similarity index 88% rename from src/main/java/org/openapitools/jackson/nullable/JsonNullableSerializers.java rename to src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2Serializers.java index db6b1ff..cb92ed0 100644 --- a/src/main/java/org/openapitools/jackson/nullable/JsonNullableSerializers.java +++ b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2Serializers.java @@ -8,7 +8,7 @@ import com.fasterxml.jackson.databind.ser.Serializers; import com.fasterxml.jackson.databind.type.ReferenceType; -public class JsonNullableSerializers extends Serializers.Base { +public class JsonNullableJackson2Serializers extends Serializers.Base { @Override public JsonSerializer findReferenceSerializer(SerializationConfig config, @@ -17,7 +17,7 @@ public JsonSerializer findReferenceSerializer(SerializationConfig config, if (JsonNullable.class.isAssignableFrom(refType.getRawClass())) { boolean staticTyping = (contentTypeSerializer == null) && config.isEnabled(MapperFeature.USE_STATIC_TYPING); - return new JsonNullableSerializer(refType, staticTyping, + return new JsonNullableJackson2Serializer(refType, staticTyping, contentTypeSerializer, contentValueSerializer); } return null; diff --git a/src/main/java/org/openapitools/jackson/nullable/JsonNullableTypeModifier.java b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2TypeModifier.java similarity index 92% rename from src/main/java/org/openapitools/jackson/nullable/JsonNullableTypeModifier.java rename to src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2TypeModifier.java index 7e7663d..0f392b3 100644 --- a/src/main/java/org/openapitools/jackson/nullable/JsonNullableTypeModifier.java +++ b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson2TypeModifier.java @@ -8,7 +8,7 @@ import java.lang.reflect.Type; -public class JsonNullableTypeModifier extends TypeModifier { +public class JsonNullableJackson2TypeModifier extends TypeModifier { @Override public JavaType modifyType(JavaType type, Type jdkType, TypeBindings bindings, TypeFactory typeFactory) diff --git a/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3BeanPropertyWriter.java b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3BeanPropertyWriter.java new file mode 100644 index 0000000..4c1d88b --- /dev/null +++ b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3BeanPropertyWriter.java @@ -0,0 +1,38 @@ +package org.openapitools.jackson.nullable; + +import tools.jackson.core.JsonGenerator; +import tools.jackson.databind.PropertyName; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ser.BeanPropertyWriter; +import tools.jackson.databind.util.NameTransformer; + +public class JsonNullableJackson3BeanPropertyWriter extends BeanPropertyWriter { + + protected JsonNullableJackson3BeanPropertyWriter(BeanPropertyWriter base) { + super(base); + } + + protected JsonNullableJackson3BeanPropertyWriter(BeanPropertyWriter base, PropertyName newName) { + super(base, newName); + } + + @Override + protected BeanPropertyWriter _new(PropertyName newName) { + return new JsonNullableJackson3BeanPropertyWriter(this, newName); + } + + @Override + public BeanPropertyWriter unwrappingWriter(NameTransformer unwrapper) { + return new UnwrappingJsonNullableJackson3BeanPropertyWriter(this, unwrapper); + } + + @Override + public void serializeAsProperty(Object bean, JsonGenerator jgen, SerializationContext ctxt) throws Exception { + Object value = get(bean); + if (JsonNullable.undefined().equals(value) || (_nullSerializer == null && value == null)) { + return; + } + super.serializeAsProperty(bean, jgen, ctxt); + } + +} \ No newline at end of file diff --git a/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3Deserializer.java b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3Deserializer.java new file mode 100644 index 0000000..a01ac80 --- /dev/null +++ b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3Deserializer.java @@ -0,0 +1,93 @@ +package org.openapitools.jackson.nullable; + + +import tools.jackson.core.JacksonException; +import tools.jackson.core.JsonParser; +import tools.jackson.core.JsonToken; +import tools.jackson.databind.DeserializationConfig; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JavaType; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.deser.ValueInstantiator; +import tools.jackson.databind.deser.std.ReferenceTypeDeserializer; +import tools.jackson.databind.jsontype.TypeDeserializer; +import tools.jackson.databind.type.ReferenceType; + +public class JsonNullableJackson3Deserializer extends ReferenceTypeDeserializer> { + + + private boolean isStringDeserializer = false; + + /* + /********************************************************** + /* Life-cycle + /********************************************************** + */ + public JsonNullableJackson3Deserializer(JavaType fullType, ValueInstantiator inst, + TypeDeserializer typeDeser, ValueDeserializer deser) { + super(fullType, inst, typeDeser, deser); + if (fullType instanceof ReferenceType && ((ReferenceType) fullType).getReferencedType() != null) { + this.isStringDeserializer = ((ReferenceType) fullType).getReferencedType().isTypeOrSubTypeOf(String.class); + } + } + + /* + /********************************************************** + /* Abstract method implementations + /********************************************************** + */ + + @Override + public JsonNullable deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException { + JsonToken t = p.currentToken(); + if (t == JsonToken.VALUE_STRING && !isStringDeserializer) { + String str = p.getString().trim(); + if (str.isEmpty()) { + return JsonNullable.undefined(); + } + } + return super.deserialize(p, ctxt); + } + + @Override + protected ReferenceTypeDeserializer> withResolved(TypeDeserializer typeDeser, ValueDeserializer valueDeser) { + return new JsonNullableJackson3Deserializer(_fullType, _valueInstantiator, + typeDeser, valueDeser); + } + + @Override + public Object getAbsentValue(DeserializationContext ctxt) { + return JsonNullable.undefined(); + } + + @Override + public JsonNullable getNullValue(DeserializationContext ctxt) { + return JsonNullable.of(null); + } + + @Override + public Object getEmptyValue(DeserializationContext ctxt) { + return JsonNullable.undefined(); + } + + @Override + public JsonNullable referenceValue(Object contents) { + return JsonNullable.of(contents); + } + + @Override + public Object getReferenced(JsonNullable reference) { + return reference.get(); + } + + @Override + public JsonNullable updateReference(JsonNullable reference, Object contents) { + return JsonNullable.of(contents); + } + + @Override + public Boolean supportsUpdate(DeserializationConfig config) { + // yes; regardless of value deserializer reference itself may be updated + return Boolean.TRUE; + } +} \ No newline at end of file diff --git a/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3Deserializers.java b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3Deserializers.java new file mode 100644 index 0000000..7ef853c --- /dev/null +++ b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3Deserializers.java @@ -0,0 +1,23 @@ +package org.openapitools.jackson.nullable; + +import tools.jackson.databind.DeserializationConfig; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.BeanDescription.Supplier; +import tools.jackson.databind.deser.Deserializers; +import tools.jackson.databind.jsontype.TypeDeserializer; +import tools.jackson.databind.type.ReferenceType; + +public class JsonNullableJackson3Deserializers extends Deserializers.Base { + + @Override + public ValueDeserializer findReferenceDeserializer(ReferenceType refType, + DeserializationConfig config, Supplier beanDescRef, + TypeDeserializer contentTypeDeserializer, ValueDeserializer contentDeserializer) { + return (refType.hasRawClass(JsonNullable.class)) ? new JsonNullableJackson3Deserializer(refType, null, contentTypeDeserializer,contentDeserializer) : null; + } + + @Override + public boolean hasDeserializerFor(DeserializationConfig config, Class valueType) { + return JsonNullable.class.equals(valueType); + } +} \ No newline at end of file diff --git a/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3Module.java b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3Module.java new file mode 100644 index 0000000..e9abf49 --- /dev/null +++ b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3Module.java @@ -0,0 +1,39 @@ +package org.openapitools.jackson.nullable; + +import tools.jackson.core.Version; +import tools.jackson.core.json.PackageVersion; +import tools.jackson.databind.JacksonModule; + +public class JsonNullableJackson3Module extends JacksonModule { + + private final String NAME = "JsonNullableModule"; + + @Override + public void setupModule(SetupContext context) { + context.addSerializers(new JsonNullableJackson3Serializers()); + context.addDeserializers(new JsonNullableJackson3Deserializers()); + // Modify type info for JsonNullable + context.addTypeModifier(new JsonNullableJackson3TypeModifier()); + context.addSerializerModifier(new JsonNullableJackson3ValueSerializerModifier()); + } + + @Override + public Version version() { + return PackageVersion.VERSION; + } + + @Override + public int hashCode() { + return NAME.hashCode(); + } + + @Override + public boolean equals(Object o) { + return this == o; + } + + @Override + public String getModuleName() { + return NAME; + } +} \ No newline at end of file diff --git a/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3Serializer.java b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3Serializer.java new file mode 100644 index 0000000..5c91ac2 --- /dev/null +++ b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3Serializer.java @@ -0,0 +1,72 @@ +package org.openapitools.jackson.nullable; + +import tools.jackson.databind.BeanProperty; +import tools.jackson.databind.ValueSerializer; +import tools.jackson.databind.jsontype.TypeSerializer; +import tools.jackson.databind.ser.std.ReferenceTypeSerializer; +import tools.jackson.databind.type.ReferenceType; +import tools.jackson.databind.util.NameTransformer; + +public class JsonNullableJackson3Serializer extends ReferenceTypeSerializer> { + + private static final long serialVersionUID = 1L; + + /* + /********************************************************** + /* Constructors, factory methods + /********************************************************** + */ + + protected JsonNullableJackson3Serializer(ReferenceType fullType, boolean staticTyping, + TypeSerializer vts, ValueSerializer ser) { + super(fullType, staticTyping, vts, ser); + } + + protected JsonNullableJackson3Serializer(JsonNullableJackson3Serializer base, BeanProperty property, + TypeSerializer vts, ValueSerializer valueSer, NameTransformer unwrapper, + Object suppressableValue) + { + // Keep suppressNulls to false to always serialize JsonNullable[null] + super(base, property, vts, valueSer, unwrapper, + suppressableValue, false); + } + + @Override + protected ReferenceTypeSerializer> withResolved(BeanProperty prop, + TypeSerializer vts, ValueSerializer valueSer, + NameTransformer unwrapper) + { + return new JsonNullableJackson3Serializer(this, prop, vts, valueSer, unwrapper, + _suppressableValue); + } + + @Override + public ReferenceTypeSerializer> withContentInclusion(Object suppressableValue, + boolean suppressNulls) + { + return new JsonNullableJackson3Serializer(this, _property, _valueTypeSerializer, + _valueSerializer, _unwrapper, + suppressableValue); + } + + /* + /********************************************************** + /* Abstract method impls + /********************************************************** + */ + + @Override + protected boolean _isValuePresent(JsonNullable value) { + return value.isPresent(); + } + + @Override + protected Object _getReferenced(JsonNullable value) { + return value.get(); + } + + @Override + protected Object _getReferencedIfPresent(JsonNullable value) { + return value.isPresent() ? value.get() : null; + } +} \ No newline at end of file diff --git a/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3Serializers.java b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3Serializers.java new file mode 100644 index 0000000..fdf826c --- /dev/null +++ b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3Serializers.java @@ -0,0 +1,27 @@ +package org.openapitools.jackson.nullable; + + +import com.fasterxml.jackson.annotation.JsonFormat.Value; + +import tools.jackson.databind.MapperFeature; +import tools.jackson.databind.SerializationConfig; +import tools.jackson.databind.ValueSerializer; +import tools.jackson.databind.BeanDescription.Supplier; +import tools.jackson.databind.jsontype.TypeSerializer; +import tools.jackson.databind.ser.Serializers; +import tools.jackson.databind.type.ReferenceType; + +public class JsonNullableJackson3Serializers extends Serializers.Base { + @Override + public ValueSerializer findReferenceSerializer(SerializationConfig config, + ReferenceType refType, Supplier beanDescRef, Value formatOverrides, + TypeSerializer contentTypeSerializer, ValueSerializer contentValueSerializer) { + if (JsonNullable.class.isAssignableFrom(refType.getRawClass())) { + boolean staticTyping = (contentTypeSerializer == null) + && config.isEnabled(MapperFeature.USE_STATIC_TYPING); + return new JsonNullableJackson3Serializer(refType, staticTyping, + contentTypeSerializer, contentValueSerializer); + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3TypeModifier.java b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3TypeModifier.java new file mode 100644 index 0000000..8a4a63c --- /dev/null +++ b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3TypeModifier.java @@ -0,0 +1,30 @@ +package org.openapitools.jackson.nullable; + +import tools.jackson.databind.JavaType; +import tools.jackson.databind.type.ReferenceType; +import tools.jackson.databind.type.TypeBindings; +import tools.jackson.databind.type.TypeFactory; +import tools.jackson.databind.type.TypeModifier; + +import java.lang.reflect.Type; + +public class JsonNullableJackson3TypeModifier extends TypeModifier { + + @Override + public JavaType modifyType(JavaType type, Type jdkType, TypeBindings bindings, TypeFactory typeFactory) + { + if (type.isReferenceType() || type.isContainerType()) { + return type; + } + final Class raw = type.getRawClass(); + + JavaType refType; + + if (raw == JsonNullable.class) { + refType = type.containedTypeOrUnknown(0); + } else { + return type; + } + return ReferenceType.upgradeFrom(type, refType); + } +} \ No newline at end of file diff --git a/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3ValueSerializerModifier.java b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3ValueSerializerModifier.java new file mode 100644 index 0000000..c20b1fb --- /dev/null +++ b/src/main/java/org/openapitools/jackson/nullable/JsonNullableJackson3ValueSerializerModifier.java @@ -0,0 +1,27 @@ +package org.openapitools.jackson.nullable; + +import tools.jackson.databind.JavaType; +import tools.jackson.databind.SerializationConfig; +import tools.jackson.databind.BeanDescription.Supplier; +import tools.jackson.databind.ser.BeanPropertyWriter; +import tools.jackson.databind.ser.ValueSerializerModifier; + +import java.util.List; + +public class JsonNullableJackson3ValueSerializerModifier extends ValueSerializerModifier +{ + @Override + public List changeProperties(SerializationConfig config, + Supplier beanDesc, + List beanProperties) + { + for (int i = 0; i < beanProperties.size(); ++i) { + final BeanPropertyWriter writer = beanProperties.get(i); + JavaType type = writer.getType(); + if (type.isTypeOrSubTypeOf(JsonNullable.class)) { + beanProperties.set(i, new JsonNullableJackson3BeanPropertyWriter(writer)); + } + } + return beanProperties; + } +} \ No newline at end of file diff --git a/src/main/java/org/openapitools/jackson/nullable/JsonNullableModule.java b/src/main/java/org/openapitools/jackson/nullable/JsonNullableModule.java index 2101f83..0b9fdd0 100644 --- a/src/main/java/org/openapitools/jackson/nullable/JsonNullableModule.java +++ b/src/main/java/org/openapitools/jackson/nullable/JsonNullableModule.java @@ -10,11 +10,11 @@ public class JsonNullableModule extends Module { @Override public void setupModule(SetupContext context) { - context.addSerializers(new JsonNullableSerializers()); - context.addDeserializers(new JsonNullableDeserializers()); + context.addSerializers(new JsonNullableJackson2Serializers()); + context.addDeserializers(new JsonNullableJackson2Deserializers()); // Modify type info for JsonNullable - context.addTypeModifier(new JsonNullableTypeModifier()); - context.addBeanSerializerModifier(new JsonNullableBeanSerializerModifier()); + context.addTypeModifier(new JsonNullableJackson2TypeModifier()); + context.addBeanSerializerModifier(new JsonNullableJackson2BeanSerializerModifier()); } @Override diff --git a/src/main/java/org/openapitools/jackson/nullable/UnwrappingJsonNullableBeanPropertyWriter.java b/src/main/java/org/openapitools/jackson/nullable/UnwrappingJsonNullableJackson2BeanPropertyWriter.java similarity index 63% rename from src/main/java/org/openapitools/jackson/nullable/UnwrappingJsonNullableBeanPropertyWriter.java rename to src/main/java/org/openapitools/jackson/nullable/UnwrappingJsonNullableJackson2BeanPropertyWriter.java index a9d1052..5235138 100644 --- a/src/main/java/org/openapitools/jackson/nullable/UnwrappingJsonNullableBeanPropertyWriter.java +++ b/src/main/java/org/openapitools/jackson/nullable/UnwrappingJsonNullableJackson2BeanPropertyWriter.java @@ -7,24 +7,24 @@ import com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanPropertyWriter; import com.fasterxml.jackson.databind.util.NameTransformer; -public class UnwrappingJsonNullableBeanPropertyWriter extends UnwrappingBeanPropertyWriter +public class UnwrappingJsonNullableJackson2BeanPropertyWriter extends UnwrappingBeanPropertyWriter { private static final long serialVersionUID = 1L; - public UnwrappingJsonNullableBeanPropertyWriter(BeanPropertyWriter base, - NameTransformer transformer) { + public UnwrappingJsonNullableJackson2BeanPropertyWriter(BeanPropertyWriter base, + NameTransformer transformer) { super(base, transformer); } - protected UnwrappingJsonNullableBeanPropertyWriter(UnwrappingBeanPropertyWriter base, - NameTransformer transformer, SerializedString name) { + protected UnwrappingJsonNullableJackson2BeanPropertyWriter(UnwrappingBeanPropertyWriter base, + NameTransformer transformer, SerializedString name) { super(base, transformer, name); } @Override protected UnwrappingBeanPropertyWriter _new(NameTransformer transformer, SerializedString newName) { - return new UnwrappingJsonNullableBeanPropertyWriter(this, transformer, newName); + return new UnwrappingJsonNullableJackson2BeanPropertyWriter(this, transformer, newName); } @Override diff --git a/src/main/java/org/openapitools/jackson/nullable/UnwrappingJsonNullableJackson3BeanPropertyWriter.java b/src/main/java/org/openapitools/jackson/nullable/UnwrappingJsonNullableJackson3BeanPropertyWriter.java new file mode 100644 index 0000000..275ed02 --- /dev/null +++ b/src/main/java/org/openapitools/jackson/nullable/UnwrappingJsonNullableJackson3BeanPropertyWriter.java @@ -0,0 +1,36 @@ +package org.openapitools.jackson.nullable; + +import tools.jackson.core.JsonGenerator; +import tools.jackson.core.io.SerializedString; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ser.BeanPropertyWriter; +import tools.jackson.databind.ser.bean.UnwrappingBeanPropertyWriter; +import tools.jackson.databind.util.NameTransformer; + +public class UnwrappingJsonNullableJackson3BeanPropertyWriter extends UnwrappingBeanPropertyWriter { + + + public UnwrappingJsonNullableJackson3BeanPropertyWriter(BeanPropertyWriter base, + NameTransformer transformer) { + super(base, transformer); + } + + protected UnwrappingJsonNullableJackson3BeanPropertyWriter(UnwrappingBeanPropertyWriter base, + NameTransformer transformer, SerializedString name) { + super(base, transformer, name); + } + + @Override + protected UnwrappingBeanPropertyWriter _new(NameTransformer transformer, SerializedString newName) { + return new UnwrappingJsonNullableJackson3BeanPropertyWriter(this, transformer, newName); + } + + @Override + public void serializeAsProperty(Object bean, JsonGenerator gen, SerializationContext prov) throws Exception { + Object value = get(bean); + if (JsonNullable.undefined().equals(value) || (_nullSerializer == null && value == null)) { + return; + } + super.serializeAsProperty(bean, gen, prov); + } +} \ No newline at end of file diff --git a/src/main/java17/module-info.java b/src/main/java17/module-info.java new file mode 100644 index 0000000..2bff50c --- /dev/null +++ b/src/main/java17/module-info.java @@ -0,0 +1,16 @@ +import org.openapitools.jackson.nullable.JsonNullableModule; +import org.openapitools.jackson.nullable.JsonNullableJackson3Module; + +module org.openapitools.jackson.nullable { + requires static com.fasterxml.jackson.databind; + requires static tools.jackson.databind; + requires static jakarta.validation; + requires static java.validation; + + exports org.openapitools.jackson.nullable; + + provides com.fasterxml.jackson.databind.Module with JsonNullableModule; + provides tools.jackson.databind.JacksonModule with JsonNullableJackson3Module; + provides javax.validation.valueextraction.ValueExtractor with org.openapitools.jackson.nullable.JsonNullableValueExtractor; + provides jakarta.validation.valueextraction.ValueExtractor with org.openapitools.jackson.nullable.JsonNullableJakartaValueExtractor; +} diff --git a/src/main/java9/module-info.java b/src/main/java9/module-info.java index 5f475f1..31b80d6 100644 --- a/src/main/java9/module-info.java +++ b/src/main/java9/module-info.java @@ -1,3 +1,5 @@ +import org.openapitools.jackson.nullable.JsonNullableModule; + module org.openapitools.jackson.nullable { requires com.fasterxml.jackson.databind; requires static jakarta.validation; diff --git a/src/main/resources/META-INF/services/tools.jackson.databind.JacksonModule b/src/main/resources/META-INF/services/tools.jackson.databind.JacksonModule new file mode 100644 index 0000000..86f8499 --- /dev/null +++ b/src/main/resources/META-INF/services/tools.jackson.databind.JacksonModule @@ -0,0 +1 @@ +org.openapitools.jackson.nullable.JsonNullableJackson3Module \ No newline at end of file diff --git a/src/test/java/org/openapitools/jackson/nullable/ContextualJsonNullableTest.java b/src/test/java/org/openapitools/jackson/nullable/ContextualJsonNullableTest.java index 2ab84d3..c5614cc 100644 --- a/src/test/java/org/openapitools/jackson/nullable/ContextualJsonNullableTest.java +++ b/src/test/java/org/openapitools/jackson/nullable/ContextualJsonNullableTest.java @@ -2,8 +2,8 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.text.SimpleDateFormat; import java.util.Date; @@ -11,18 +11,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -class ContextualJsonNullableTest extends ModuleTestBase -{ +class ContextualJsonNullableTest extends ModuleTestBase { // [datatypes-java8#17] - @JsonPropertyOrder({ "date", "date1", "date2" }) - static class ContextualJsonNullables - { + @JsonPropertyOrder({"date", "date1", "date2"}) + static class ContextualJsonNullables { public JsonNullable date; - @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy+MM+dd") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy+MM+dd") public JsonNullable date1; - @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy*MM*dd") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy*MM*dd") public JsonNullable date2; } @@ -32,20 +30,19 @@ static class ContextualJsonNullables /********************************************************** */ - @Test - void testContextualJsonNullables() throws Exception - { - final ObjectMapper mapper = mapperWithModule(); + @ParameterizedTest + @MethodSource("jsonProcessors") + void testContextualJsonNullables(JsonProcessor jsonProcessor) throws Exception { SimpleDateFormat df = new SimpleDateFormat("yyyy/MM/dd"); df.setTimeZone(TimeZone.getTimeZone("UTC")); - mapper.setDateFormat(df); + jsonProcessor.mapperWithModule().setDateFormat(df); ContextualJsonNullables input = new ContextualJsonNullables(); input.date = JsonNullable.of(new Date(0L)); input.date1 = JsonNullable.of(new Date(0L)); input.date2 = JsonNullable.of(new Date(0L)); - final String json = mapper.writeValueAsString(input); + final String json = jsonProcessor.writeValueAsString(input); assertEquals(aposToQuotes( - "{'date':'1970/01/01','date1':'1970+01+01','date2':'1970*01*01'}"), + "{'date':'1970/01/01','date1':'1970+01+01','date2':'1970*01*01'}"), json); } } diff --git a/src/test/java/org/openapitools/jackson/nullable/CreatorTest.java b/src/test/java/org/openapitools/jackson/nullable/CreatorTest.java index 722198f..186baae 100644 --- a/src/test/java/org/openapitools/jackson/nullable/CreatorTest.java +++ b/src/test/java/org/openapitools/jackson/nullable/CreatorTest.java @@ -2,25 +2,22 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.*; // TODO: fix JsonNullable in constructor annotated by JsonCreator @Disabled("JsonNullable in a constructor is deserialized to JsonNullable[null] instead of JsonNullable.undefined") -class CreatorTest extends ModuleTestBase -{ - static class CreatorWithJsonNullableStrings - { +class CreatorTest extends ModuleTestBase { + static class CreatorWithJsonNullableStrings { JsonNullable a, b; // note: something weird with test setup, should not need annotations @JsonCreator public CreatorWithJsonNullableStrings(@JsonProperty("a") JsonNullable a, - @JsonProperty("b") JsonNullable b) - { + @JsonProperty("b") JsonNullable b) { this.a = a; this.b = b; } @@ -32,16 +29,15 @@ public CreatorWithJsonNullableStrings(@JsonProperty("a") JsonNullable a, /********************************************************** */ - private final ObjectMapper MAPPER = mapperWithModule(); - /** * Test to ensure that creator parameters use defaulting * (introduced in Jackson 2.6) */ - @Test - void testCreatorWithJsonNullable() throws Exception - { - CreatorWithJsonNullableStrings bean = MAPPER.readValue( + @ParameterizedTest + @MethodSource("jsonProcessors") + void testCreatorWithJsonNullable(JsonProcessor jsonProcessor) throws Exception { + jsonProcessor.mapperWithModule(); + CreatorWithJsonNullableStrings bean = jsonProcessor.readValue( aposToQuotes("{'a':'foo'}"), CreatorWithJsonNullableStrings.class); assertNotNull(bean); assertNotNull(bean.a); diff --git a/src/test/java/org/openapitools/jackson/nullable/Jackson2Processor.java b/src/test/java/org/openapitools/jackson/nullable/Jackson2Processor.java new file mode 100644 index 0000000..65a94e5 --- /dev/null +++ b/src/test/java/org/openapitools/jackson/nullable/Jackson2Processor.java @@ -0,0 +1,169 @@ +package org.openapitools.jackson.nullable; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; +import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer; + +import java.io.IOException; +import java.io.Serializable; +import java.text.SimpleDateFormat; + +public class Jackson2Processor implements JsonProcessor { + + ObjectMapper mapper; + + public Jackson2Processor() { + } + + @Override + public JsonProcessor mapperWithModule() { + mapper = new ObjectMapper(); + mapper.registerModule(new JsonNullableModule()); + return this; + } + + @Override + public JsonProcessor setDateFormat(SimpleDateFormat simpleDateFormat) { + mapper.setDateFormat(simpleDateFormat); + return this; + } + + @Override + public JsonProcessor setDefaultPropertyInclusion(JsonInclude.Include incl) { + mapper.setDefaultPropertyInclusion(incl); + return this; + } + + @Override + public JsonProcessor setDefaultPropertyInclusion(JsonInclude.Value incl) { + mapper.setDefaultPropertyInclusion(incl); + return this; + } + + @Override + public JsonProcessor objectAndNonConcreteTyping() { + mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE); + return this; + } + + @Override + public String writeValueAsString(Object obj) throws JsonProcessingException { + return mapper.writeValueAsString(obj); + } + + @Override + public T readValue(String string, Class type) throws JsonProcessingException { + return mapper.readValue(string, type); + } + + @Override + public T readValue(String string, Object typeReference) throws Exception { + return mapper.readValue(string, (TypeReference) typeReference); + } + + public static class Jackson2TypeDescriptor implements TypeDescriptor { + + private final JavaType javaType; + + public Jackson2TypeDescriptor(JavaType javaType) { + this.javaType = javaType; + } + + @Override + public boolean isReferenceType() { + return javaType.isReferenceType(); + } + + @Override + public Class getRawClass() { + return javaType.getRawClass(); + } + } + + @Override + public TypeDescriptor constructType(Class type) { + return new Jackson2TypeDescriptor(mapper.constructType(type)); + } + + public static class Jackson2CaseChangingStringWrapper implements CaseChangingStringWrapper { + @JsonSerialize(contentUsing = UpperCasingSerializer.class) + @JsonDeserialize(contentUsing = LowerCasingDeserializer.class) + public JsonNullable value = JsonNullable.undefined(); + + public Jackson2CaseChangingStringWrapper() { + } + + public Jackson2CaseChangingStringWrapper(String value) { + this.value = JsonNullable.of(value); + } + + @Override + public JsonNullable getValue() { + return this.value; + } + } + + public static class UpperCasingSerializer extends StdScalarSerializer { + public UpperCasingSerializer() { + super(String.class); + } + + @Override + public void serialize(String value, JsonGenerator gen, + SerializerProvider provider) throws IOException { + gen.writeString(value.toUpperCase()); + } + } + + public static class LowerCasingDeserializer extends StdScalarDeserializer { + public LowerCasingDeserializer() { + super(String.class); + } + + @Override + public String deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + return p.getText().toLowerCase(); + } + } + + @Override + public CaseChangingStringWrapper getCaseChangingStringWrapper(String str) { + return new Jackson2CaseChangingStringWrapper(str); + } + + @Override + public CaseChangingStringWrapper getCaseChangingStringWrapper() { + return new Jackson2CaseChangingStringWrapper(); + } + + @Override + public Class getCaseChangingStringWrapperClass() { + return Jackson2CaseChangingStringWrapper.class; + } + + public static class Jackson2AbstractJsonNullable implements AbstractJsonNullable { + @JsonDeserialize(contentAs=Integer.class) + public JsonNullable value; + + @Override + public JsonNullable getValue() { + return this.value; + } + } + + @Override + public Class getAbstractJsonNullableClass() { + return Jackson2AbstractJsonNullable.class; + } +} diff --git a/src/test/java/org/openapitools/jackson/nullable/Jackson3Processor.java b/src/test/java/org/openapitools/jackson/nullable/Jackson3Processor.java new file mode 100644 index 0000000..e8a1c3c --- /dev/null +++ b/src/test/java/org/openapitools/jackson/nullable/Jackson3Processor.java @@ -0,0 +1,173 @@ +package org.openapitools.jackson.nullable; + +import com.fasterxml.jackson.annotation.JsonInclude; +import tools.jackson.core.JsonGenerator; +import tools.jackson.core.JsonParser; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.*; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonSerialize; +import tools.jackson.databind.deser.std.StdScalarDeserializer; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator; +import tools.jackson.databind.jsontype.PolymorphicTypeValidator; +import tools.jackson.databind.ser.std.StdScalarSerializer; + +import java.io.Serializable; +import java.text.SimpleDateFormat; + +public class Jackson3Processor implements JsonProcessor { + + JsonMapper.Builder builder; + ObjectMapper mapper; + + public Jackson3Processor() { + } + + @Override + public JsonProcessor mapperWithModule() { + builder = JsonMapper.builder().addModule(new JsonNullableJackson3Module()); + return this; + } + + @Override + public JsonProcessor setDateFormat(SimpleDateFormat simpleDateFormat) { + builder.defaultDateFormat(simpleDateFormat); + return this; + } + + @Override + public JsonProcessor setDefaultPropertyInclusion(JsonInclude.Include incl) { + builder.changeDefaultPropertyInclusion(include -> include.withValueInclusion(incl)); + return this; + } + + @Override + public JsonProcessor setDefaultPropertyInclusion(JsonInclude.Value incl) { + builder.changeDefaultPropertyInclusion(include -> include + .withValueInclusion(incl.getValueInclusion()) + .withContentInclusion(incl.getContentInclusion())); + return this; + } + + @Override + public JsonProcessor objectAndNonConcreteTyping() { + PolymorphicTypeValidator validator = BasicPolymorphicTypeValidator.builder().allowIfBaseType(Object.class).build(); + builder.activateDefaultTyping(validator, DefaultTyping.OBJECT_AND_NON_CONCRETE); + return this; + } + + @Override + public String writeValueAsString(Object obj) throws Exception { + mapper = builder.build(); + return mapper.writeValueAsString(obj); + } + + @Override + public T readValue(String string, Class type) throws Exception { + mapper = builder.build(); + return mapper.readValue(string, type); + } + + @Override + public T readValue(String string, Object typeReference) throws Exception { + mapper = builder.build(); + return mapper.readValue(string, (TypeReference) typeReference); + } + + public static class Jackson3TypeDescriptor implements TypeDescriptor { + + private final JavaType javaType; + + public Jackson3TypeDescriptor(JavaType javaType) { + this.javaType = javaType; + } + + @Override + public boolean isReferenceType() { + return javaType.isReferenceType(); + } + + @Override + public Class getRawClass() { + return javaType.getRawClass(); + } + } + + @Override + public TypeDescriptor constructType(Class type) { + mapper = builder.build(); + return new Jackson3TypeDescriptor(mapper.constructType(type)); + } + + public static class Jackson3CaseChangingStringWrapper implements CaseChangingStringWrapper { + @JsonSerialize(contentUsing = UpperCasingSerializer.class) + @JsonDeserialize(contentUsing = LowerCasingDeserializer.class) + public JsonNullable value = JsonNullable.undefined(); + + public Jackson3CaseChangingStringWrapper() { + } + + public Jackson3CaseChangingStringWrapper(String value) { + this.value = JsonNullable.of(value); + } + + @Override + public JsonNullable getValue() { + return this.value; + } + } + + public static class UpperCasingSerializer extends StdScalarSerializer { + public UpperCasingSerializer() { + super(String.class); + } + + @Override + public void serialize(String value, JsonGenerator gen, + SerializationContext context) { + gen.writeString(value.toUpperCase()); + } + } + + public static class LowerCasingDeserializer extends StdScalarDeserializer { + public LowerCasingDeserializer() { + super(String.class); + } + + @Override + public String deserialize(JsonParser p, DeserializationContext ctxt) { + return p.getText().toLowerCase(); + } + } + + @Override + public CaseChangingStringWrapper getCaseChangingStringWrapper(String str) { + return new Jackson3CaseChangingStringWrapper(str); + } + + @Override + public CaseChangingStringWrapper getCaseChangingStringWrapper() { + return new Jackson3CaseChangingStringWrapper(); + } + + @Override + public Class getCaseChangingStringWrapperClass() { + return Jackson3CaseChangingStringWrapper.class; + } + + public static class Jackson3AbstractJsonNullable implements AbstractJsonNullable { + @JsonDeserialize(contentAs=Integer.class) + public JsonNullable value; + + @Override + public JsonNullable getValue() { + return this.value; + } + } + + @Override + public Class getAbstractJsonNullableClass() { + return Jackson3AbstractJsonNullable.class; + } +} diff --git a/src/test/java/org/openapitools/jackson/nullable/JsonNullJacksonServiceLoadingTest.java b/src/test/java/org/openapitools/jackson/nullable/JsonNullJacksonServiceLoadingTest.java index 29aa6a6..b85d292 100644 --- a/src/test/java/org/openapitools/jackson/nullable/JsonNullJacksonServiceLoadingTest.java +++ b/src/test/java/org/openapitools/jackson/nullable/JsonNullJacksonServiceLoadingTest.java @@ -1,16 +1,22 @@ package org.openapitools.jackson.nullable; import com.fasterxml.jackson.databind.ObjectMapper; - import org.junit.jupiter.api.Test; +import tools.jackson.databind.cfg.MapperBuilder; import static org.junit.jupiter.api.Assertions.assertEquals; class JsonNullJacksonServiceLoadingTest { @Test - void testJacksonJsonNullableModuleServiceLoading() { + void testJackson2JsonNullableModuleServiceLoading() { String foundModuleName = ObjectMapper.findModules().get(0).getModuleName(); assertEquals(new JsonNullableModule().getModuleName(), foundModuleName); } + + @Test + void testJackson3JsonNullableModuleServiceLoading() { + String foundModuleName = MapperBuilder.findModules().get(0).getModuleName(); + assertEquals(new JsonNullableJackson3Module().getModuleName(), foundModuleName); + } } \ No newline at end of file diff --git a/src/test/java/org/openapitools/jackson/nullable/JsonNullWithEmptyTest.java b/src/test/java/org/openapitools/jackson/nullable/JsonNullWithEmptyTest.java index 2308246..69907e1 100644 --- a/src/test/java/org/openapitools/jackson/nullable/JsonNullWithEmptyTest.java +++ b/src/test/java/org/openapitools/jackson/nullable/JsonNullWithEmptyTest.java @@ -1,28 +1,41 @@ package org.openapitools.jackson.nullable; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; -class JsonNullWithEmptyTest extends ModuleTestBase -{ - private final ObjectMapper MAPPER = mapperWithModule(); +@ParameterizedClass +@MethodSource("jsonProcessors") +class JsonNullWithEmptyTest extends ModuleTestBase { + + @Parameter + JsonProcessor jsonProcessor; static class BooleanBean { public JsonNullable value; - public BooleanBean() { } + public BooleanBean() { + } + public BooleanBean(Boolean b) { value = JsonNullable.of(b); } } + @BeforeEach + void setup() { + jsonProcessor.mapperWithModule(); + } + @Test void testJsonNullableFromEmpty() throws Exception { - JsonNullable value = MAPPER.readValue(quote(""), new TypeReference>() {}); + JsonNullable value = jsonProcessor.readValue(quote(""), TypeReferences.INTEGER.getType(jsonProcessor)); assertFalse(value.isPresent()); } @@ -31,10 +44,29 @@ void testJsonNullableFromEmpty() throws Exception { void testBooleanWithEmpty() throws Exception { // and looks like a special, somewhat non-conforming case is what a user had // issues with - BooleanBean b = MAPPER.readValue(aposToQuotes("{'value':''}"), BooleanBean.class); + BooleanBean b = jsonProcessor.readValue(aposToQuotes("{'value':''}"), BooleanBean.class); assertNotNull(b.value); assertFalse(b.value.isPresent()); } + private enum TypeReferences { + INTEGER { + @Override + public Object getType(JsonProcessor jsonProcessor) { + if (jsonProcessor instanceof Jackson2Processor) { + return new TypeReference>() { + }; + } + if (jsonProcessor instanceof Jackson3Processor) { + return new tools.jackson.core.type.TypeReference>() { + }; + } + throw new RuntimeException("jsonProcessor type not implemented"); + } + }; + + public abstract Object getType(JsonProcessor jsonProcessor); + } + } \ No newline at end of file diff --git a/src/test/java/org/openapitools/jackson/nullable/JsonNullableBasicTest.java b/src/test/java/org/openapitools/jackson/nullable/JsonNullableBasicTest.java index 13c947c..3daa019 100644 --- a/src/test/java/org/openapitools/jackson/nullable/JsonNullableBasicTest.java +++ b/src/test/java/org/openapitools/jackson/nullable/JsonNullableBasicTest.java @@ -2,15 +2,19 @@ import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.*; +@ParameterizedClass +@MethodSource("jsonProcessors") class JsonNullableBasicTest extends ModuleTestBase { public static final class JsonNullableData { @@ -22,9 +26,83 @@ public static final class JsonNullableGenericData { private JsonNullable myData; } - @JsonIdentityInfo(generator= ObjectIdGenerators.IntSequenceGenerator.class) - public static class Unit - { + private enum TypeReferences { + STRING { + @Override + public Object getType(JsonProcessor jsonProcessor) { + if (jsonProcessor instanceof Jackson2Processor) { + return new TypeReference>() { + }; + } + if (jsonProcessor instanceof Jackson3Processor) { + return new tools.jackson.core.type.TypeReference>() { + }; + } + throw new RuntimeException("jsonProcessor type not implemented"); + } + }, + INTEGER { + @Override + public Object getType(JsonProcessor jsonProcessor) { + if (jsonProcessor instanceof Jackson2Processor) { + return new TypeReference>() { + }; + } + if (jsonProcessor instanceof Jackson3Processor) { + return new tools.jackson.core.type.TypeReference>() { + }; + } + throw new RuntimeException("jsonProcessor type not implemented"); + } + }, + JSON_NULLABLE_DATA { + @Override + public Object getType(JsonProcessor jsonProcessor) { + if (jsonProcessor instanceof Jackson2Processor) { + return new TypeReference>() { + }; + } + if (jsonProcessor instanceof Jackson3Processor) { + return new tools.jackson.core.type.TypeReference>() { + }; + } + throw new RuntimeException("jsonProcessor type not implemented"); + } + }, + JSON_NULLABLE_GENERIC_DATA { + @Override + public Object getType(JsonProcessor jsonProcessor) { + if (jsonProcessor instanceof Jackson2Processor) { + return new TypeReference>>() { + }; + } + if (jsonProcessor instanceof Jackson3Processor) { + return new tools.jackson.core.type.TypeReference>>() { + }; + } + throw new RuntimeException("jsonProcessor type not implemented"); + } + }, + LIST_JSON_NULLABLE_STRING { + @Override + public Object getType(JsonProcessor jsonProcessor) { + if (jsonProcessor instanceof Jackson2Processor) { + return new TypeReference>>() { + }; + } + if (jsonProcessor instanceof Jackson3Processor) { + return new tools.jackson.core.type.TypeReference>>() { + }; + } + throw new RuntimeException("jsonProcessor type not implemented"); + } + }; + + public abstract Object getType(JsonProcessor jsonProcessor); + } + + @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class) + public static class Unit { public JsonNullable baseUnit; public Unit() { @@ -49,9 +127,11 @@ public static class Container { @JsonSubTypes({ @JsonSubTypes.Type(name = "ContainedImpl", value = ContainedImpl.class), }) - public static interface Contained { } + public interface Contained { + } - public static class ContainedImpl implements Contained { } + public static class ContainedImpl implements Contained { + } /* /********************************************************** @@ -59,12 +139,18 @@ public static class ContainedImpl implements Contained { } /********************************************************** */ - private final ObjectMapper MAPPER = mapperWithModule(); + @Parameter + JsonProcessor jsonProcessor; + + @BeforeEach + void setUp() { + jsonProcessor.mapperWithModule(); + } @Test void testJsonNullableTypeResolution() { // With 2.6, we need to recognize it as ReferenceType - JavaType t = MAPPER.constructType(JsonNullable.class); + JsonProcessor.TypeDescriptor t = jsonProcessor.constructType(JsonNullable.class); assertNotNull(t); assertEquals(JsonNullable.class, t.getRawClass()); assertTrue(t.isReferenceType()); @@ -72,24 +158,24 @@ void testJsonNullableTypeResolution() { @Test void testDeserAbsent() throws Exception { - JsonNullable value = MAPPER.readValue("null", - new TypeReference>() { - }); + Object type = TypeReferences.STRING.getType(jsonProcessor); + JsonNullable value = jsonProcessor.readValue("null", + type); assertNull(value.get()); } @Test void testDeserSimpleString() throws Exception { - JsonNullable value = MAPPER.readValue("\"simpleString\"", - new TypeReference>() { - }); + Object type = TypeReferences.STRING.getType(jsonProcessor); + JsonNullable value = jsonProcessor.readValue("\"simpleString\"", + type); assertTrue(value.isPresent()); assertEquals("simpleString", value.get()); } @Test void testDeserInsideObject() throws Exception { - JsonNullableData data = MAPPER.readValue("{\"myString\":\"simpleString\"}", + JsonNullableData data = jsonProcessor.readValue("{\"myString\":\"simpleString\"}", JsonNullableData.class); assertTrue(data.myString.isPresent()); assertEquals("simpleString", data.myString.get()); @@ -97,9 +183,8 @@ void testDeserInsideObject() throws Exception { @Test void testDeserComplexObject() throws Exception { - TypeReference> type = new TypeReference>() { - }; - JsonNullable data = MAPPER.readValue( + Object type = TypeReferences.JSON_NULLABLE_DATA.getType(jsonProcessor); + JsonNullable data = jsonProcessor.readValue( "{\"myString\":\"simpleString\"}", type); assertTrue(data.isPresent()); assertTrue(data.get().myString.isPresent()); @@ -108,9 +193,8 @@ void testDeserComplexObject() throws Exception { @Test void testDeserGeneric() throws Exception { - TypeReference>> type = new TypeReference>>() { - }; - JsonNullable> data = MAPPER.readValue( + Object type = TypeReferences.JSON_NULLABLE_GENERIC_DATA.getType(jsonProcessor); + JsonNullable> data = jsonProcessor.readValue( "{\"myData\":\"simpleString\"}", type); assertTrue(data.isPresent()); assertTrue(data.get().myData.isPresent()); @@ -119,13 +203,13 @@ void testDeserGeneric() throws Exception { @Test void testSerAbsent() throws Exception { - String value = MAPPER.writeValueAsString(JsonNullable.undefined()); + String value = jsonProcessor.writeValueAsString(JsonNullable.undefined()); assertEquals("null", value); } @Test void testSerSimpleString() throws Exception { - String value = MAPPER.writeValueAsString(JsonNullable.of("simpleString")); + String value = jsonProcessor.writeValueAsString(JsonNullable.of("simpleString")); assertEquals("\"simpleString\"", value); } @@ -133,7 +217,7 @@ void testSerSimpleString() throws Exception { void testSerInsideObject() throws Exception { JsonNullableData data = new JsonNullableData(); data.myString = JsonNullable.of("simpleString"); - String value = MAPPER.writeValueAsString(data); + String value = jsonProcessor.writeValueAsString(data); assertEquals("{\"myString\":\"simpleString\"}", value); } @@ -141,7 +225,7 @@ void testSerInsideObject() throws Exception { void testSerComplexObject() throws Exception { JsonNullableData data = new JsonNullableData(); data.myString = JsonNullable.of("simpleString"); - String value = MAPPER.writeValueAsString(JsonNullable.of(data)); + String value = jsonProcessor.writeValueAsString(JsonNullable.of(data)); assertEquals("{\"myString\":\"simpleString\"}", value); } @@ -149,7 +233,7 @@ void testSerComplexObject() throws Exception { void testSerGeneric() throws Exception { JsonNullableGenericData data = new JsonNullableGenericData(); data.myData = JsonNullable.of("simpleString"); - String value = MAPPER.writeValueAsString(JsonNullable.of(data)); + String value = jsonProcessor.writeValueAsString(JsonNullable.of(data)); assertEquals("{\"myData\":\"simpleString\"}", value); } @@ -157,8 +241,7 @@ void testSerGeneric() throws Exception { void testSerOptDefault() throws Exception { JsonNullableData data = new JsonNullableData(); data.myString = JsonNullable.undefined(); - String value = mapperWithModule().setDefaultPropertyInclusion( - JsonInclude.Include.ALWAYS).writeValueAsString(data); + String value = jsonProcessor.setDefaultPropertyInclusion(JsonInclude.Include.ALWAYS).writeValueAsString(data); assertEquals("{}", value); } @@ -166,8 +249,7 @@ void testSerOptDefault() throws Exception { void testSerOptNull() throws Exception { JsonNullableData data = new JsonNullableData(); data.myString = null; - String value = mapperWithModule().setDefaultPropertyInclusion( - JsonInclude.Include.NON_NULL).writeValueAsString(data); + String value = jsonProcessor.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL).writeValueAsString(data); assertEquals("{}", value); } @@ -175,8 +257,7 @@ void testSerOptNull() throws Exception { void testSerOptNullNulled() throws Exception { JsonNullableData data = new JsonNullableData(); data.myString = JsonNullable.of(null); - String value = mapperWithModule().setDefaultPropertyInclusion( - JsonInclude.Include.NON_NULL).writeValueAsString(data); + String value = jsonProcessor.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL).writeValueAsString(data); assertEquals("{\"myString\":null}", value); } @@ -185,28 +266,28 @@ void testSerOptAbsent() throws Exception { final JsonNullableData data = new JsonNullableData(); data.myString = JsonNullable.undefined(); - ObjectMapper mapper = mapperWithModule() - .setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL); - - assertEquals("{}", mapper.writeValueAsString(data)); + assertEquals("{}", jsonProcessor + .setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL) + .writeValueAsString(data)); // but do exclude with NON_EMPTY - mapper = mapperWithModule() - .setDefaultPropertyInclusion(JsonInclude.Include.NON_EMPTY); - assertEquals("{}", mapper.writeValueAsString(data)); + assertEquals("{}", jsonProcessor + .setDefaultPropertyInclusion(JsonInclude.Include.NON_EMPTY) + .writeValueAsString(data)); // and with new (2.6) NON_ABSENT - mapper = mapperWithModule() - .setDefaultPropertyInclusion(JsonInclude.Include.NON_ABSENT); - assertEquals("{}", mapper.writeValueAsString(data)); + assertEquals("{}", jsonProcessor + .setDefaultPropertyInclusion(JsonInclude.Include.NON_ABSENT) + .writeValueAsString(data)); } @Test void testSerOptAbsentNull() throws Exception { JsonNullableData data = new JsonNullableData(); data.myString = JsonNullable.of(null); - String value = mapperWithModule().setDefaultPropertyInclusion( - JsonInclude.Include.NON_ABSENT).writeValueAsString(data); + String value = jsonProcessor + .setDefaultPropertyInclusion(JsonInclude.Include.NON_ABSENT) + .writeValueAsString(data); assertEquals("{\"myString\":null}", value); } @@ -214,23 +295,20 @@ void testSerOptAbsentNull() throws Exception { void testSerOptNonEmpty() throws Exception { JsonNullableData data = new JsonNullableData(); data.myString = null; - String value = mapperWithModule().setDefaultPropertyInclusion( - JsonInclude.Include.NON_EMPTY).writeValueAsString(data); + String value = jsonProcessor + .setDefaultPropertyInclusion(JsonInclude.Include.NON_EMPTY) + .writeValueAsString(data); assertEquals("{}", value); } @Test void testWithTypingEnabled() throws Exception { - final ObjectMapper objectMapper = mapperWithModule(); - // ENABLE TYPING - objectMapper - .activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE); - + jsonProcessor.objectAndNonConcreteTyping(); final JsonNullableData myData = new JsonNullableData(); myData.myString = JsonNullable.of("abc"); - final String json = objectMapper.writeValueAsString(myData); - final JsonNullableData deserializedMyData = objectMapper.readValue(json, + final String json = jsonProcessor.writeValueAsString(myData); + final JsonNullableData deserializedMyData = jsonProcessor.readValue(json, JsonNullableData.class); assertEquals(myData.myString, deserializedMyData.myString); } @@ -239,8 +317,8 @@ void testWithTypingEnabled() throws Exception { void testObjectId() throws Exception { final Unit input = new Unit(); input.link(input); - String json = MAPPER.writeValueAsString(input); - Unit result = MAPPER.readValue(json, Unit.class); + String json = jsonProcessor.writeValueAsString(input); + Unit result = jsonProcessor.readValue(json, Unit.class); assertNotNull(result); assertNotNull(result.baseUnit); assertTrue(result.baseUnit.isPresent()); @@ -251,27 +329,26 @@ void testObjectId() throws Exception { @Test void testJsonNullableCollection() throws Exception { - TypeReference>> typeReference = new TypeReference>>() { - }; + Object typeReference = TypeReferences.LIST_JSON_NULLABLE_STRING.getType(jsonProcessor); List> list = new ArrayList>(); list.add(JsonNullable.of("2014-1-22")); list.add(JsonNullable.of(null)); list.add(JsonNullable.of("2014-1-23")); - String str = MAPPER.writeValueAsString(list); + String str = jsonProcessor.writeValueAsString(list); assertEquals("[\"2014-1-22\",null,\"2014-1-23\"]", str); - List> result = MAPPER.readValue(str, typeReference); + List> result = jsonProcessor.readValue(str, typeReference); assertEquals(list.size(), result.size()); for (int i = 0; i < list.size(); ++i) { - assertEquals(list.get(i), result.get(i),"Entry #" + i); + assertEquals(list.get(i), result.get(i), "Entry #" + i); } } @Test void testDeserNull() throws Exception { - JsonNullable value = MAPPER.readValue("\"\"", new TypeReference>() {}); + JsonNullable value = jsonProcessor.readValue("\"\"", TypeReferences.INTEGER.getType(jsonProcessor)); assertFalse(value.isPresent()); } @@ -280,9 +357,9 @@ void testPolymorphic() throws Exception { final Container dto = new Container(); dto.contained = JsonNullable.of((Contained) new ContainedImpl()); - final String json = MAPPER.writeValueAsString(dto); + final String json = jsonProcessor.writeValueAsString(dto); - final Container fromJson = MAPPER.readValue(json, Container.class); + final Container fromJson = jsonProcessor.readValue(json, Container.class); assertNotNull(fromJson.contained); assertTrue(fromJson.contained.isPresent()); assertSame(ContainedImpl.class, fromJson.contained.get().getClass()); diff --git a/src/test/java/org/openapitools/jackson/nullable/JsonNullableInclusionTest.java b/src/test/java/org/openapitools/jackson/nullable/JsonNullableInclusionTest.java index 54c9c6a..7b9d906 100644 --- a/src/test/java/org/openapitools/jackson/nullable/JsonNullableInclusionTest.java +++ b/src/test/java/org/openapitools/jackson/nullable/JsonNullableInclusionTest.java @@ -1,30 +1,36 @@ package org.openapitools.jackson.nullable; import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.util.LinkedHashMap; import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; -class JsonNullableInclusionTest extends ModuleTestBase -{ - @JsonAutoDetect(fieldVisibility=Visibility.ANY) +@ParameterizedClass +@MethodSource("jsonProcessors") +class JsonNullableInclusionTest extends ModuleTestBase { + @JsonAutoDetect(fieldVisibility = Visibility.ANY) public static final class JsonNullableData { public JsonNullable myString = JsonNullable.undefined(); } // for [datatype-jdk8#18] static class JsonNullableNonEmptyStringBean { - @JsonInclude(value=Include.NON_EMPTY, content=Include.NON_EMPTY) + @JsonInclude(value = Include.NON_EMPTY, content = Include.NON_EMPTY) public JsonNullable value; - public JsonNullableNonEmptyStringBean() { } + public JsonNullableNonEmptyStringBean() { + } + JsonNullableNonEmptyStringBean(String str) { value = JsonNullable.of(str); } @@ -32,6 +38,7 @@ public JsonNullableNonEmptyStringBean() { } public static final class JsonNullableGenericData { public JsonNullable myData; + public static JsonNullableGenericData construct(T data) { JsonNullableGenericData ret = new JsonNullableGenericData(); ret.myData = JsonNullable.of(data); @@ -54,13 +61,19 @@ public OptMapBean(String key, JsonNullable v) { /********************************************************** */ - private final ObjectMapper MAPPER = mapperWithModule(); + @Parameter + JsonProcessor jsonProcessor; + + @BeforeEach + void setUp() { + jsonProcessor.mapperWithModule(); + } @Test void testSerOptNonEmpty() throws Exception { JsonNullableData data = new JsonNullableData(); data.myString = null; - String value = mapperWithModule().setDefaultPropertyInclusion( + String value = jsonProcessor.setDefaultPropertyInclusion( JsonInclude.Include.NON_EMPTY).writeValueAsString(data); assertEquals("{}", value); } @@ -69,7 +82,7 @@ void testSerOptNonEmpty() throws Exception { void testSerOptNonDefault() throws Exception { JsonNullableData data = new JsonNullableData(); data.myString = null; - String value = mapperWithModule().setDefaultPropertyInclusion( + String value = jsonProcessor.setDefaultPropertyInclusion( JsonInclude.Include.NON_DEFAULT).writeValueAsString(data); assertEquals("{}", value); } @@ -78,18 +91,18 @@ void testSerOptNonDefault() throws Exception { void testSerOptNonAbsent() throws Exception { JsonNullableData data = new JsonNullableData(); data.myString = null; - String value = mapperWithModule().setDefaultPropertyInclusion( + String value = jsonProcessor.setDefaultPropertyInclusion( JsonInclude.Include.NON_ABSENT).writeValueAsString(data); assertEquals("{}", value); } @Test void testExcludeEmptyStringViaJsonNullable() throws Exception { - String json = MAPPER.writeValueAsString(new JsonNullableNonEmptyStringBean("x")); + String json = jsonProcessor.writeValueAsString(new JsonNullableNonEmptyStringBean("x")); assertEquals("{\"value\":\"x\"}", json); - json = MAPPER.writeValueAsString(new JsonNullableNonEmptyStringBean(null)); + json = jsonProcessor.writeValueAsString(new JsonNullableNonEmptyStringBean(null)); assertEquals("{\"value\":null}", json); - json = MAPPER.writeValueAsString(new JsonNullableNonEmptyStringBean("")); + json = jsonProcessor.writeValueAsString(new JsonNullableNonEmptyStringBean("")); assertEquals("{}", json); } @@ -97,50 +110,50 @@ void testExcludeEmptyStringViaJsonNullable() throws Exception { void testSerPropInclusionAlways() throws Exception { JsonInclude.Value incl = JsonInclude.Value.construct(JsonInclude.Include.NON_ABSENT, JsonInclude.Include.ALWAYS); - ObjectMapper mapper = mapperWithModule().setDefaultPropertyInclusion(incl); + jsonProcessor.setDefaultPropertyInclusion(incl); assertEquals("{\"myData\":true}", - mapper.writeValueAsString(JsonNullableGenericData.construct(Boolean.TRUE))); + jsonProcessor.writeValueAsString(JsonNullableGenericData.construct(Boolean.TRUE))); } @Test void testSerPropInclusionNonNull() throws Exception { JsonInclude.Value incl = JsonInclude.Value.construct(JsonInclude.Include.NON_ABSENT, JsonInclude.Include.NON_NULL); - ObjectMapper mapper = mapperWithModule().setDefaultPropertyInclusion(incl); + jsonProcessor.setDefaultPropertyInclusion(incl); assertEquals("{\"myData\":true}", - mapper.writeValueAsString(JsonNullableGenericData.construct(Boolean.TRUE))); + jsonProcessor.writeValueAsString(JsonNullableGenericData.construct(Boolean.TRUE))); } @Test void testSerPropInclusionNonAbsent() throws Exception { JsonInclude.Value incl = JsonInclude.Value.construct(JsonInclude.Include.NON_ABSENT, JsonInclude.Include.NON_ABSENT); - ObjectMapper mapper = mapperWithModule().setDefaultPropertyInclusion(incl); + jsonProcessor.setDefaultPropertyInclusion(incl); assertEquals("{\"myData\":true}", - mapper.writeValueAsString(JsonNullableGenericData.construct(Boolean.TRUE))); + jsonProcessor.writeValueAsString(JsonNullableGenericData.construct(Boolean.TRUE))); } @Test void testSerPropInclusionNonEmpty() throws Exception { JsonInclude.Value incl = JsonInclude.Value.construct(JsonInclude.Include.NON_ABSENT, JsonInclude.Include.NON_EMPTY); - ObjectMapper mapper = mapperWithModule().setDefaultPropertyInclusion(incl); + jsonProcessor.setDefaultPropertyInclusion(incl); assertEquals("{\"myData\":true}", - mapper.writeValueAsString(JsonNullableGenericData.construct(Boolean.TRUE))); + jsonProcessor.writeValueAsString(JsonNullableGenericData.construct(Boolean.TRUE))); } @Test void testMapElementInclusion() throws Exception { - ObjectMapper mapper = mapperWithModule().setDefaultPropertyInclusion( + jsonProcessor.setDefaultPropertyInclusion( JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_ABSENT)); // first: Absent entry/-ies should NOT be included assertEquals("{\"values\":{}}", - mapper.writeValueAsString(new OptMapBean("key", JsonNullable.undefined()))); + jsonProcessor.writeValueAsString(new OptMapBean("key", JsonNullable.undefined()))); // but non-empty should assertEquals("{\"values\":{\"key\":\"value\"}}", - mapper.writeValueAsString(new OptMapBean("key", JsonNullable.of("value")))); + jsonProcessor.writeValueAsString(new OptMapBean("key", JsonNullable.of("value")))); // and actually even empty assertEquals("{\"values\":{\"key\":\"\"}}", - mapper.writeValueAsString(new OptMapBean("key", JsonNullable.of("")))); + jsonProcessor.writeValueAsString(new OptMapBean("key", JsonNullable.of("")))); } } diff --git a/src/test/java/org/openapitools/jackson/nullable/JsonNullableSimpleTest.java b/src/test/java/org/openapitools/jackson/nullable/JsonNullableSimpleTest.java index 0af9291..73a427f 100644 --- a/src/test/java/org/openapitools/jackson/nullable/JsonNullableSimpleTest.java +++ b/src/test/java/org/openapitools/jackson/nullable/JsonNullableSimpleTest.java @@ -1,27 +1,25 @@ package org.openapitools.jackson.nullable; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; -import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.NoSuchElementException; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; final class JsonNullableSimpleTest { - private ObjectMapper mapper; - - @BeforeEach - void setup() { - mapper = new ObjectMapper(); - mapper.registerModule(new JsonNullableModule()); + static Stream jsonProcessors() { + return Stream.of( + new Jackson2Processor().mapperWithModule(), + new Jackson3Processor().mapperWithModule() + ); } @Test @@ -81,44 +79,49 @@ void ifPresentWithNonNullValuePresent() { })); } - @Test - void serializeNonBeanProperty() throws JsonProcessingException { - assertEquals("null", mapper.writeValueAsString(JsonNullable.of(null))); - assertEquals("\"foo\"", mapper.writeValueAsString(JsonNullable.of("foo"))); + @ParameterizedTest + @MethodSource("jsonProcessors") + void serializeNonBeanProperty(JsonProcessor jsonProcessor) throws Exception { + assertEquals("null", jsonProcessor.writeValueAsString(JsonNullable.of(null))); + assertEquals("\"foo\"", jsonProcessor.writeValueAsString(JsonNullable.of("foo"))); // TODO: Serialize non bean JsonNullable.undefined to empty string - assertEquals("null", mapper.writeValueAsString(JsonNullable.undefined())); + assertEquals("null", jsonProcessor.writeValueAsString(JsonNullable.undefined())); } - @Test - void serializeAlways() throws JsonProcessingException { - assertEquals("{}", mapper.writeValueAsString(new Pet().name(JsonNullable.undefined()))); - assertEquals("{\"name\":null}", mapper.writeValueAsString(new Pet().name(null))); - assertEquals("{\"name\":null}", mapper.writeValueAsString(new Pet().name(JsonNullable.of(null)))); - assertEquals("{\"name\":\"Rex\"}", mapper.writeValueAsString(new Pet().name(JsonNullable.of("Rex")))); + @ParameterizedTest + @MethodSource("jsonProcessors") + void serializeAlways(JsonProcessor jsonProcessor) throws Exception { + assertEquals("{}", jsonProcessor.writeValueAsString(new Pet().name(JsonNullable.undefined()))); + assertEquals("{\"name\":null}", jsonProcessor.writeValueAsString(new Pet().name(null))); + assertEquals("{\"name\":null}", jsonProcessor.writeValueAsString(new Pet().name(JsonNullable.of(null)))); + assertEquals("{\"name\":\"Rex\"}", jsonProcessor.writeValueAsString(new Pet().name(JsonNullable.of("Rex")))); } - @Test - void serializeNonNull() throws JsonProcessingException { - mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL); - assertEquals("{}", mapper.writeValueAsString(new Pet().name(JsonNullable.undefined()))); - assertEquals("{}", mapper.writeValueAsString(new Pet().name(null))); - assertEquals("{\"name\":null}", mapper.writeValueAsString(new Pet().name(JsonNullable.of(null)))); - assertEquals("{\"name\":\"Rex\"}", mapper.writeValueAsString(new Pet().name(JsonNullable.of("Rex")))); + @ParameterizedTest + @MethodSource("jsonProcessors") + void serializeNonNull(JsonProcessor jsonProcessor) throws Exception { + jsonProcessor.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL); + assertEquals("{}", jsonProcessor.writeValueAsString(new Pet().name(JsonNullable.undefined()))); + assertEquals("{}", jsonProcessor.writeValueAsString(new Pet().name(null))); + assertEquals("{\"name\":null}", jsonProcessor.writeValueAsString(new Pet().name(JsonNullable.of(null)))); + assertEquals("{\"name\":\"Rex\"}", jsonProcessor.writeValueAsString(new Pet().name(JsonNullable.of("Rex")))); } - @Test - void serializeNonAbsent() throws JsonProcessingException { - mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_ABSENT); - assertEquals("{}", mapper.writeValueAsString(new Pet().name(JsonNullable.undefined()))); - assertEquals("{}", mapper.writeValueAsString(new Pet().name(null))); - assertEquals("{\"name\":null}", mapper.writeValueAsString(new Pet().name(JsonNullable.of(null)))); - assertEquals("{\"name\":\"Rex\"}", mapper.writeValueAsString(new Pet().name(JsonNullable.of("Rex")))); + @ParameterizedTest + @MethodSource("jsonProcessors") + void serializeNonAbsent(JsonProcessor jsonProcessor) throws Exception { + jsonProcessor.setDefaultPropertyInclusion(JsonInclude.Include.NON_ABSENT); + assertEquals("{}", jsonProcessor.writeValueAsString(new Pet().name(JsonNullable.undefined()))); + assertEquals("{}", jsonProcessor.writeValueAsString(new Pet().name(null))); + assertEquals("{\"name\":null}", jsonProcessor.writeValueAsString(new Pet().name(JsonNullable.of(null)))); + assertEquals("{\"name\":\"Rex\"}", jsonProcessor.writeValueAsString(new Pet().name(JsonNullable.of("Rex")))); } - @Test - void serializeCollection() throws JsonProcessingException { - assertEquals("[\"foo\",null,null,null]", mapper.writeValueAsString(Arrays.asList( + @ParameterizedTest + @MethodSource("jsonProcessors") + void serializeCollection(JsonProcessor jsonProcessor) throws Exception { + assertEquals("[\"foo\",null,null,null]", jsonProcessor.writeValueAsString(Arrays.asList( JsonNullable.of("foo"), JsonNullable.of(null), JsonNullable.undefined(), @@ -126,66 +129,62 @@ void serializeCollection() throws JsonProcessingException { ))); } - @Test - void deserializeStringMembers() throws IOException { - testReadPetName(JsonNullable.of("Rex"), "{\"name\":\"Rex\"}"); - testReadPetName(JsonNullable.of(null), "{\"name\":null}"); - testReadPetName(JsonNullable.of(""), "{\"name\":\"\"}"); - testReadPetName(JsonNullable.of(" "), "{\"name\":\" \"}"); - testReadPetName(JsonNullable.undefined(), "{}"); - } - - @Test - void deserializeNonStringMembers() throws IOException { - testReadPetAge(JsonNullable.of(Integer.valueOf(15)), "{\"age\":\"15\"}"); - testReadPetAge(JsonNullable.of(null), "{\"age\":null}"); - testReadPetAge(JsonNullable.undefined(), "{\"age\":\"\"}"); - testReadPetAge(JsonNullable.undefined(), "{\"age\":\" \"}"); - testReadPetAge(JsonNullable.undefined(), "{}"); - } - - @Test - void deserializeStringNonBeanMembers() throws IOException { - assertEquals(JsonNullable.of(null), mapper.readValue("null", new TypeReference>() { - })); - assertEquals(JsonNullable.of("42"), mapper.readValue("\"42\"", new TypeReference>() { - })); - assertEquals(JsonNullable.of(""), mapper.readValue("\"\"", new TypeReference>() { - })); - assertEquals(JsonNullable.of(" "), mapper.readValue("\" \"", new TypeReference>() { - })); - } - - @Test - void deserializeNonStringNonBeanMembers() throws IOException { - assertEquals(JsonNullable.of(null), mapper.readValue("\"null\"", new TypeReference>() { - })); - assertEquals(JsonNullable.of(42), mapper.readValue("\"42\"", new TypeReference>() { - })); - assertEquals(JsonNullable.undefined(), mapper.readValue("\"\"", new TypeReference>() { - })); - assertEquals(JsonNullable.undefined(), mapper.readValue("\" \"", new TypeReference>() { - })); - } - - @Test - void deserializeCollection() throws IOException { - List> values = mapper.readValue("[\"foo\", null]", - new TypeReference>>() { - }); + @ParameterizedTest + @MethodSource("jsonProcessors") + void deserializeStringMembers(JsonProcessor jsonProcessor) throws Exception { + testReadPetName(jsonProcessor, JsonNullable.of("Rex"), "{\"name\":\"Rex\"}"); + testReadPetName(jsonProcessor, JsonNullable.of(null), "{\"name\":null}"); + testReadPetName(jsonProcessor, JsonNullable.of(""), "{\"name\":\"\"}"); + testReadPetName(jsonProcessor, JsonNullable.of(" "), "{\"name\":\" \"}"); + testReadPetName(jsonProcessor, JsonNullable.undefined(), "{}"); + } + + @ParameterizedTest + @MethodSource("jsonProcessors") + void deserializeNonStringMembers(JsonProcessor jsonProcessor) throws Exception { + testReadPetAge(jsonProcessor, JsonNullable.of(Integer.valueOf(15)), "{\"age\":\"15\"}"); + testReadPetAge(jsonProcessor, JsonNullable.of(null), "{\"age\":null}"); + testReadPetAge(jsonProcessor, JsonNullable.undefined(), "{\"age\":\"\"}"); + testReadPetAge(jsonProcessor, JsonNullable.undefined(), "{\"age\":\" \"}"); + testReadPetAge(jsonProcessor, JsonNullable.undefined(), "{}"); + } + + @ParameterizedTest + @MethodSource("jsonProcessors") + void deserializeStringNonBeanMembers(JsonProcessor jsonProcessor) throws Exception { + assertEquals(JsonNullable.of(null), jsonProcessor.readValue("null", TypeReferences.STRING.getType(jsonProcessor))); + assertEquals(JsonNullable.of("42"), jsonProcessor.readValue("\"42\"", TypeReferences.STRING.getType(jsonProcessor))); + assertEquals(JsonNullable.of(""), jsonProcessor.readValue("\"\"", TypeReferences.STRING.getType(jsonProcessor))); + assertEquals(JsonNullable.of(" "), jsonProcessor.readValue("\" \"", TypeReferences.STRING.getType(jsonProcessor))); + } + + @ParameterizedTest + @MethodSource("jsonProcessors") + void deserializeNonStringNonBeanMembers(JsonProcessor jsonProcessor) throws Exception { + assertEquals(JsonNullable.of(null), jsonProcessor.readValue("\"null\"", TypeReferences.INTEGER.getType(jsonProcessor))); + assertEquals(JsonNullable.of(42), jsonProcessor.readValue("\"42\"", TypeReferences.INTEGER.getType(jsonProcessor))); + assertEquals(JsonNullable.undefined(), jsonProcessor.readValue("\"\"", TypeReferences.INTEGER.getType(jsonProcessor))); + assertEquals(JsonNullable.undefined(), jsonProcessor.readValue("\" \"", TypeReferences.INTEGER.getType(jsonProcessor))); + } + + @ParameterizedTest + @MethodSource("jsonProcessors") + void deserializeCollection(JsonProcessor jsonProcessor) throws Exception { + List> values = jsonProcessor.readValue("[\"foo\", null]", + TypeReferences.LIST_NULLABLE_STRING.getType(jsonProcessor)); assertEquals(2, values.size()); assertEquals(JsonNullable.of("foo"), values.get(0)); assertEquals(JsonNullable.of(null), values.get(1)); } - private void testReadPetName(JsonNullable expected, String json) throws IOException { - Pet pet = mapper.readValue(json, Pet.class); + private void testReadPetName(JsonProcessor jsonProcessor, JsonNullable expected, String json) throws Exception { + Pet pet = jsonProcessor.readValue(json, Pet.class); JsonNullable name = pet.name; assertEquals(expected, name); } - private void testReadPetAge(JsonNullable expected, String json) throws IOException { - Pet pet = mapper.readValue(json, Pet.class); + private void testReadPetAge(JsonProcessor jsonProcessor, JsonNullable expected, String json) throws Exception { + Pet pet = jsonProcessor.readValue(json, Pet.class); JsonNullable age = pet.age; assertEquals(expected, age); } @@ -205,4 +204,51 @@ public Pet age(JsonNullable age) { return this; } } + + private enum TypeReferences { + STRING { + @Override + public Object getType(JsonProcessor jsonProcessor) { + if (jsonProcessor instanceof Jackson2Processor) { + return new TypeReference>() { + }; + } + if (jsonProcessor instanceof Jackson3Processor) { + return new tools.jackson.core.type.TypeReference>() { + }; + } + throw new RuntimeException("jsonProcessor type not implemented"); + } + }, + INTEGER { + @Override + public Object getType(JsonProcessor jsonProcessor) { + if (jsonProcessor instanceof Jackson2Processor) { + return new TypeReference>() { + }; + } + if (jsonProcessor instanceof Jackson3Processor) { + return new tools.jackson.core.type.TypeReference>() { + }; + } + throw new RuntimeException("jsonProcessor type not implemented"); + } + }, + LIST_NULLABLE_STRING { + @Override + public Object getType(JsonProcessor jsonProcessor) { + if (jsonProcessor instanceof Jackson2Processor) { + return new TypeReference>>() { + }; + } + if (jsonProcessor instanceof Jackson3Processor) { + return new tools.jackson.core.type.TypeReference>>() { + }; + } + throw new RuntimeException("jsonProcessor type not implemented"); + } + }; + + public abstract Object getType(JsonProcessor jsonProcessor); + } } diff --git a/src/test/java/org/openapitools/jackson/nullable/JsonNullableTest.java b/src/test/java/org/openapitools/jackson/nullable/JsonNullableTest.java index 5c63af1..65cee5c 100644 --- a/src/test/java/org/openapitools/jackson/nullable/JsonNullableTest.java +++ b/src/test/java/org/openapitools/jackson/nullable/JsonNullableTest.java @@ -3,47 +3,38 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; -import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.io.IOException; import java.util.concurrent.atomic.AtomicReference; import static org.junit.jupiter.api.Assertions.*; -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class JsonNullableTest extends ModuleTestBase -{ - private static final TypeReference> JSON_NULLABLE_STRING_TYPE = new TypeReference>() {}; - private static final TypeReference> JSON_NULLABLE_BEAN_TYPE = new TypeReference>() {}; +@ParameterizedClass +@MethodSource("jsonProcessors") +class JsonNullableTest extends ModuleTestBase { - public static class TestBean - { + private Object jsonNullableStringType; + private Object jsonNullableBeanType; + + + public static class TestBean { public int foo; public String bar; @JsonCreator - public TestBean(@JsonProperty("foo") int foo, @JsonProperty("bar") String bar) - { + public TestBean(@JsonProperty("foo") int foo, @JsonProperty("bar") String bar) { this.foo = foo; this.bar = bar; } @Override - public boolean equals(Object obj) - { + public boolean equals(Object obj) { if (obj.getClass() != getClass()) { return false; } @@ -61,7 +52,9 @@ public int hashCode() { static class JsonNullableStringBean { public JsonNullable value; - public JsonNullableStringBean() { } + public JsonNullableStringBean() { + } + JsonNullableStringBean(String str) { value = JsonNullable.of(str); } @@ -78,7 +71,7 @@ public Issue4Entity(@JsonProperty("data") JsonNullable data) { this.data = data; } - @JsonProperty ("data") + @JsonProperty("data") public JsonNullable data() { return data; } @@ -94,44 +87,14 @@ public boolean equals(Object o) { } } - static class CaseChangingStringWrapper { - @JsonSerialize(contentUsing=UpperCasingSerializer.class) - @JsonDeserialize(contentUsing=LowerCasingDeserializer.class) - public JsonNullable value = JsonNullable.undefined(); - - CaseChangingStringWrapper() { } - public CaseChangingStringWrapper(String s) { value = JsonNullable.of(s); } - } - - @SuppressWarnings("serial") - public static class UpperCasingSerializer extends StdScalarSerializer - { - public UpperCasingSerializer() { super(String.class); } - - @Override - public void serialize(String value, JsonGenerator gen, - SerializerProvider provider) throws IOException { - gen.writeString(value.toUpperCase()); - } - } - - @SuppressWarnings("serial") - public static class LowerCasingDeserializer extends StdScalarDeserializer - { - public LowerCasingDeserializer() { super(String.class); } + @Parameter + JsonProcessor jsonProcessor; - @Override - public String deserialize(JsonParser p, DeserializationContext ctxt) - throws IOException, JsonProcessingException { - return p.getText().toLowerCase(); - } - } - - private ObjectMapper MAPPER; - - @BeforeAll + @BeforeEach void setUp() { - MAPPER = mapperWithModule(); + jsonProcessor.mapperWithModule(); + jsonNullableStringType = TypeReferences.STRING.getType(jsonProcessor); + jsonNullableBeanType = TypeReferences.TEST_BEAN.getType(jsonProcessor); } /* @@ -142,41 +105,42 @@ void setUp() { @Test void testStringAbsent() throws Exception { - assertNull(roundtrip(JsonNullable.undefined(), JSON_NULLABLE_STRING_TYPE).get()); + assertNull(roundtrip(JsonNullable.undefined(), jsonNullableStringType).get()); } + @Test void testStringNull() throws Exception { - assertNull(roundtrip(JsonNullable.of(null), JSON_NULLABLE_STRING_TYPE).get()); + assertNull(roundtrip(JsonNullable.of(null), jsonNullableStringType).get()); } @Test void testStringPresent() throws Exception { - assertEquals("test", roundtrip(JsonNullable.of("test"), JSON_NULLABLE_STRING_TYPE).get()); + assertEquals("test", roundtrip(JsonNullable.of("test"), jsonNullableStringType).get()); } @Test void testBeanAdsent() throws Exception { - assertNull(roundtrip(JsonNullable.undefined(), JSON_NULLABLE_BEAN_TYPE).get()); + assertNull(roundtrip(JsonNullable.undefined(), jsonNullableBeanType).get()); } @Test void testBeanNull() throws Exception { - assertNull(roundtrip(JsonNullable.of(null), JSON_NULLABLE_BEAN_TYPE).get()); + assertNull(roundtrip(JsonNullable.of(null), jsonNullableBeanType).get()); } @Test void testBeanPresent() throws Exception { final TestBean bean = new TestBean(Integer.MAX_VALUE, "woopwoopwoopwoopwoop"); - assertEquals(bean, roundtrip(JsonNullable.of(bean), JSON_NULLABLE_BEAN_TYPE).get()); + assertEquals(bean, roundtrip(JsonNullable.of(bean), jsonNullableBeanType).get()); } // [issue#4] @Test void testBeanWithCreator() throws Exception { final Issue4Entity emptyEntity = new Issue4Entity(JsonNullable.of(null)); - final String json = MAPPER.writeValueAsString(emptyEntity); + final String json = jsonProcessor.writeValueAsString(emptyEntity); - final Issue4Entity deserialisedEntity = MAPPER.readValue(json, Issue4Entity.class); + final Issue4Entity deserialisedEntity = jsonProcessor.readValue(json, Issue4Entity.class); if (!deserialisedEntity.equals(emptyEntity)) { throw new IOException("Entities not equal"); } @@ -185,7 +149,7 @@ void testBeanWithCreator() throws Exception { // [issue#4] @Test void testJsonNullableStringInBean() throws Exception { - JsonNullableStringBean bean = MAPPER.readValue("{\"value\":\"xyz\"}", JsonNullableStringBean.class); + JsonNullableStringBean bean = jsonProcessor.readValue("{\"value\":\"xyz\"}", JsonNullableStringBean.class); assertNotNull(bean.value); assertEquals("xyz", bean.value.get()); } @@ -193,28 +157,28 @@ void testJsonNullableStringInBean() throws Exception { // To support [datatype-jdk8#8] @Test void testExcludeIfJsonNullableAbsent() throws Exception { - ObjectMapper mapper = mapperWithModule() + jsonProcessor .setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL); assertEquals(aposToQuotes("{'value':'foo'}"), - mapper.writeValueAsString(new JsonNullableStringBean("foo"))); + jsonProcessor.writeValueAsString(new JsonNullableStringBean("foo"))); // absent is not strictly null so assertEquals(aposToQuotes("{'value':null}"), - mapper.writeValueAsString(new JsonNullableStringBean(null))); + jsonProcessor.writeValueAsString(new JsonNullableStringBean(null))); // however: - mapper = mapperWithModule() + jsonProcessor.mapperWithModule() .setDefaultPropertyInclusion(JsonInclude.Include.NON_ABSENT); assertEquals(aposToQuotes("{'value':'foo'}"), - mapper.writeValueAsString(new JsonNullableStringBean("foo"))); + jsonProcessor.writeValueAsString(new JsonNullableStringBean("foo"))); assertEquals(aposToQuotes("{\"value\":null}"), - mapper.writeValueAsString(new JsonNullableStringBean(null))); + jsonProcessor.writeValueAsString(new JsonNullableStringBean(null))); } @Test void testWithCustomDeserializer() throws Exception { - CaseChangingStringWrapper w = MAPPER.readValue(aposToQuotes("{'value':'FoobaR'}"), - CaseChangingStringWrapper.class); - assertEquals("foobar", w.value.get()); + JsonProcessor.CaseChangingStringWrapper w = jsonProcessor.readValue(aposToQuotes("{'value':'FoobaR'}"), + jsonProcessor.getCaseChangingStringWrapperClass()); + assertEquals("foobar", w.getValue().get()); } // [modules-java8#36] @@ -227,44 +191,44 @@ void testWithCustomDeserializerIfJsonNullableAbsent() throws Exception { CaseChangingStringWrapper.class).value); */ - assertEquals(JsonNullable.of(null), MAPPER.readValue(aposToQuotes("{'value':null}"), - CaseChangingStringWrapper.class).value); + assertEquals(JsonNullable.of(null), jsonProcessor.readValue(aposToQuotes("{'value':null}"), + jsonProcessor.getCaseChangingStringWrapperClass()).getValue()); } @Test void testCustomSerializer() throws Exception { final String VALUE = "fooBAR"; - String json = MAPPER.writeValueAsString(new CaseChangingStringWrapper(VALUE)); + String json = jsonProcessor.writeValueAsString(jsonProcessor.getCaseChangingStringWrapper(VALUE)); assertEquals(json, aposToQuotes("{'value':'FOOBAR'}")); } @Test void testCustomSerializerIfJsonNullableAbsent() throws Exception { - ObjectMapper mapper = mapperWithModule() + jsonProcessor.mapperWithModule() .setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL); assertEquals(aposToQuotes("{'value':'FOO'}"), - mapper.writeValueAsString(new CaseChangingStringWrapper("foo"))); + jsonProcessor.writeValueAsString(jsonProcessor.getCaseChangingStringWrapper("foo"))); // absent is not strictly null so assertEquals(aposToQuotes("{'value':null}"), - mapper.writeValueAsString(new CaseChangingStringWrapper(null))); + jsonProcessor.writeValueAsString(jsonProcessor.getCaseChangingStringWrapper(null))); assertEquals(aposToQuotes("{}"), - mapper.writeValueAsString(new CaseChangingStringWrapper())); + jsonProcessor.writeValueAsString(jsonProcessor.getCaseChangingStringWrapper())); // however: - mapper = mapperWithModule() + jsonProcessor.mapperWithModule() .setDefaultPropertyInclusion(JsonInclude.Include.NON_ABSENT); assertEquals(aposToQuotes("{'value':'FOO'}"), - mapper.writeValueAsString(new CaseChangingStringWrapper("foo"))); + jsonProcessor.writeValueAsString(jsonProcessor.getCaseChangingStringWrapper("foo"))); assertEquals(aposToQuotes("{'value':null}"), - mapper.writeValueAsString(new CaseChangingStringWrapper(null))); + jsonProcessor.writeValueAsString(jsonProcessor.getCaseChangingStringWrapper(null))); assertEquals(aposToQuotes("{}"), - mapper.writeValueAsString(new CaseChangingStringWrapper())); + jsonProcessor.writeValueAsString(jsonProcessor.getCaseChangingStringWrapper())); } // [modules-java8#33]: Verify against regression... @Test void testOtherRefSerializers() throws Exception { - String json = MAPPER.writeValueAsString(new AtomicReference("foo")); + String json = jsonProcessor.writeValueAsString(new AtomicReference("foo")); assertEquals(quote("foo"), json); } @@ -274,8 +238,41 @@ void testOtherRefSerializers() throws Exception { /********************************************************** */ - private JsonNullable roundtrip(JsonNullable obj, TypeReference> type) throws IOException { - String bytes = MAPPER.writeValueAsString(obj); - return MAPPER.readValue(bytes, type); + private JsonNullable roundtrip(JsonNullable obj, Object type) throws Exception { + String bytes = jsonProcessor.writeValueAsString(obj); + return jsonProcessor.readValue(bytes, type); + } + + private enum TypeReferences { + STRING { + @Override + public Object getType(JsonProcessor jsonProcessor) { + if (jsonProcessor instanceof Jackson2Processor) { + return new TypeReference>() { + }; + } + if (jsonProcessor instanceof Jackson3Processor) { + return new tools.jackson.core.type.TypeReference>() { + }; + } + throw new RuntimeException("jsonProcessor type not implemented"); + } + }, + TEST_BEAN { + @Override + public Object getType(JsonProcessor jsonProcessor) { + if (jsonProcessor instanceof Jackson2Processor) { + return new TypeReference>() { + }; + } + if (jsonProcessor instanceof Jackson3Processor) { + return new tools.jackson.core.type.TypeReference>() { + }; + } + throw new RuntimeException("jsonProcessor type not implemented"); + } + }; + + public abstract Object getType(JsonProcessor jsonProcessor); } } diff --git a/src/test/java/org/openapitools/jackson/nullable/JsonNullableUnwrappedTest.java b/src/test/java/org/openapitools/jackson/nullable/JsonNullableUnwrappedTest.java index 68ef656..74bc183 100644 --- a/src/test/java/org/openapitools/jackson/nullable/JsonNullableUnwrappedTest.java +++ b/src/test/java/org/openapitools/jackson/nullable/JsonNullableUnwrappedTest.java @@ -53,7 +53,7 @@ static class Bean2 { @Test void testUntypedWithJsonNullablesNotNulls() throws Exception { - final ObjectMapper mapper = mapperWithModule(); + final ObjectMapper mapper = mapperWithJackson2Module(); String jsonExp = aposToQuotes("{'XX.name':'Bob'}"); String jsonAct = mapper.writeValueAsString(new JsonNullableParent()); assertEquals(jsonExp, jsonAct); @@ -62,7 +62,7 @@ void testUntypedWithJsonNullablesNotNulls() throws Exception { // for [datatype-jdk8#20] @Test void testShouldSerializeUnwrappedJsonNullable() throws Exception { - final ObjectMapper mapper = mapperWithModule(); + final ObjectMapper mapper = mapperWithJackson2Module(); assertEquals("{\"id\":\"foo\"}", mapper.writeValueAsString(new Bean("foo", JsonNullable.undefined()))); @@ -71,7 +71,7 @@ void testShouldSerializeUnwrappedJsonNullable() throws Exception { // for [datatype-jdk8#26] @Test void testPropogatePrefixToSchema() throws Exception { - final ObjectMapper mapper = mapperWithModule(); + final ObjectMapper mapper = mapperWithJackson2Module(); final AtomicReference propertyName = new AtomicReference(); mapper.acceptJsonFormatVisitor(JsonNullableParent.class, new JsonFormatVisitorWrapper.Base(new DefaultSerializerProvider.Impl()) { diff --git a/src/test/java/org/openapitools/jackson/nullable/JsonProcessor.java b/src/test/java/org/openapitools/jackson/nullable/JsonProcessor.java new file mode 100644 index 0000000..857e519 --- /dev/null +++ b/src/test/java/org/openapitools/jackson/nullable/JsonProcessor.java @@ -0,0 +1,45 @@ +package org.openapitools.jackson.nullable; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.text.SimpleDateFormat; + +public interface JsonProcessor { + JsonProcessor mapperWithModule(); + + JsonProcessor setDateFormat(SimpleDateFormat simpleDateFormat); + + JsonProcessor setDefaultPropertyInclusion(JsonInclude.Include incl); + + JsonProcessor setDefaultPropertyInclusion(JsonInclude.Value incl); + + JsonProcessor objectAndNonConcreteTyping(); + + String writeValueAsString(Object obj) throws Exception; + + T readValue(String string, Class type) throws Exception; + T readValue(String string, Object typeReference) throws Exception; + + interface TypeDescriptor { + boolean isReferenceType(); + + Class getRawClass(); + } + + TypeDescriptor constructType(Class type); + + interface CaseChangingStringWrapper { + JsonNullable getValue(); + } + + CaseChangingStringWrapper getCaseChangingStringWrapper(String str); + CaseChangingStringWrapper getCaseChangingStringWrapper(); + + Class getCaseChangingStringWrapperClass(); + + interface AbstractJsonNullable { + JsonNullable getValue(); + } + + Class getAbstractJsonNullableClass(); +} diff --git a/src/test/java/org/openapitools/jackson/nullable/ModuleTestBase.java b/src/test/java/org/openapitools/jackson/nullable/ModuleTestBase.java index 102c585..ba4c722 100644 --- a/src/test/java/org/openapitools/jackson/nullable/ModuleTestBase.java +++ b/src/test/java/org/openapitools/jackson/nullable/ModuleTestBase.java @@ -1,8 +1,11 @@ package org.openapitools.jackson.nullable; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import java.util.Arrays; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.fail; @@ -14,13 +17,35 @@ abstract class ModuleTestBase /********************************************************************** */ - protected ObjectMapper mapperWithModule() + static Stream jsonProcessors() { + return Stream.of( + new Jackson2Processor(), + new Jackson3Processor() + ); + } + + protected ObjectMapper mapperWithJackson2Module() { ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JsonNullableModule()); return mapper; } + protected tools.jackson.databind.ObjectMapper mapperWithJackson3Module() + { + return mapperBuilderWithJackson3Module().build(); + } + + protected tools.jackson.databind.ObjectMapper mapperWithJackson3Module(JsonInclude.Include include) + { + return mapperBuilderWithJackson3Module().changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(include)).build(); + } + + protected JsonMapper.Builder mapperBuilderWithJackson3Module() + { + return JsonMapper.builder().addModule(new JsonNullableJackson3Module()); + } + /* /********************************************************************** /* Helper methods, setup diff --git a/src/test/java/org/openapitools/jackson/nullable/PolymorphicJsonNullableTest.java b/src/test/java/org/openapitools/jackson/nullable/PolymorphicJsonNullableTest.java index 76f8fe5..1aa9315 100644 --- a/src/test/java/org/openapitools/jackson/nullable/PolymorphicJsonNullableTest.java +++ b/src/test/java/org/openapitools/jackson/nullable/PolymorphicJsonNullableTest.java @@ -2,11 +2,16 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.*; +@ParameterizedClass +@MethodSource("jsonProcessors") class PolymorphicJsonNullableTest extends ModuleTestBase { // For [datatype-jdk8#14] public static class Container { @@ -17,11 +22,19 @@ public static class Container { @JsonSubTypes({ @JsonSubTypes.Type(name = "ContainedImpl", value = ContainedImpl.class), }) - public static interface Contained { } + public static interface Contained { + } + + public static class ContainedImpl implements Contained { + } - public static class ContainedImpl implements Contained { } + @Parameter + JsonProcessor jsonProcessor; - private final ObjectMapper MAPPER = mapperWithModule(); + @BeforeEach + void setup() { + jsonProcessor.mapperWithModule(); + } // [datatype-jdk8#14] @Test @@ -29,9 +42,9 @@ void testPolymorphic14() throws Exception { final Container dto = new Container(); dto.contained = JsonNullable.of(new ContainedImpl()); - final String json = MAPPER.writeValueAsString(dto); + final String json = jsonProcessor.writeValueAsString(dto); - final Container fromJson = MAPPER.readValue(json, Container.class); + final Container fromJson = jsonProcessor.readValue(json, Container.class); assertNotNull(fromJson.contained); assertTrue(fromJson.contained.isPresent()); assertSame(ContainedImpl.class, fromJson.contained.get().getClass()); diff --git a/src/test/java/org/openapitools/jackson/nullable/TestJsonNullableWithPolymorphic.java b/src/test/java/org/openapitools/jackson/nullable/TestJsonNullableWithPolymorphic.java index a9fe318..5e18c92 100644 --- a/src/test/java/org/openapitools/jackson/nullable/TestJsonNullableWithPolymorphic.java +++ b/src/test/java/org/openapitools/jackson/nullable/TestJsonNullableWithPolymorphic.java @@ -4,9 +4,11 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.util.LinkedHashMap; import java.util.Map; @@ -14,26 +16,33 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +@ParameterizedClass +@MethodSource("jsonProcessors") class TestJsonNullableWithPolymorphic extends ModuleTestBase { static class ContainerA { @JsonProperty private JsonNullable name = JsonNullable.undefined(); - @JsonProperty private JsonNullable strategy = JsonNullable.undefined(); + @JsonProperty + private JsonNullable strategy = JsonNullable.undefined(); } static class ContainerB { - @JsonProperty private JsonNullable name = JsonNullable.undefined(); - @JsonProperty private Strategy strategy = null; + @JsonProperty + private JsonNullable name = JsonNullable.undefined(); + @JsonProperty + private Strategy strategy = null; } @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") - @JsonSubTypes({ @JsonSubTypes.Type(name = "Foo", value = Foo.class), + @JsonSubTypes({@JsonSubTypes.Type(name = "Foo", value = Foo.class), @JsonSubTypes.Type(name = "Bar", value = Bar.class), - @JsonSubTypes.Type(name = "Baz", value = Baz.class) }) - interface Strategy { } + @JsonSubTypes.Type(name = "Baz", value = Baz.class)}) + interface Strategy { + } static class Foo implements Strategy { - @JsonProperty private final int foo; + @JsonProperty + private final int foo; @JsonCreator Foo(@JsonProperty("foo") int foo) { @@ -42,7 +51,8 @@ static class Foo implements Strategy { } static class Bar implements Strategy { - @JsonProperty private final boolean bar; + @JsonProperty + private final boolean bar; @JsonCreator Bar(@JsonProperty("bar") boolean bar) { @@ -51,7 +61,8 @@ static class Bar implements Strategy { } static class Baz implements Strategy { - @JsonProperty private final String baz; + @JsonProperty + private final String baz; @JsonCreator Baz(@JsonProperty("baz") String baz) { @@ -59,18 +70,19 @@ static class Baz implements Strategy { } } - static class AbstractJsonNullable { - @JsonDeserialize(contentAs=Integer.class) - public JsonNullable value; - } - /* /********************************************************** /* Test methods /********************************************************** */ - final ObjectMapper MAPPER = mapperWithModule(); + @Parameter + JsonProcessor jsonProcessor; + + @BeforeEach + void setup() { + jsonProcessor.mapperWithModule(); + } @Test void testJsonNullableMapsFoo() throws Exception { @@ -83,7 +95,7 @@ void testJsonNullableMapsFoo() throws Exception { foo.put("name", "foo strategy"); foo.put("strategy", loop); - _test(MAPPER, foo); + _test(jsonProcessor, foo); } @Test @@ -97,7 +109,7 @@ void testJsonNullableMapsBar() throws Exception { bar.put("name", "bar strategy"); bar.put("strategy", loop); - _test(MAPPER, bar); + _test(jsonProcessor, bar); } @Test @@ -110,21 +122,21 @@ void testJsonNullableMapsBaz() throws Exception { baz.put("name", "bar strategy"); baz.put("strategy", loop); - _test(MAPPER, baz); + _test(jsonProcessor, baz); } @Test void testJsonNullableWithTypeAnnotation13() throws Exception { - AbstractJsonNullable result = MAPPER.readValue("{\"value\" : 5}", - AbstractJsonNullable.class); + JsonProcessor.AbstractJsonNullable result = jsonProcessor.readValue("{\"value\" : 5}", + jsonProcessor.getAbstractJsonNullableClass()); assertNotNull(result); - assertNotNull(result.value); - Object ob = result.value.get(); + assertNotNull(result.getValue()); + Object ob = result.getValue().get(); assertEquals(Integer.class, ob.getClass()); assertEquals(Integer.valueOf(5), ob); } - private void _test(ObjectMapper m, Map map) throws Exception { + private void _test(JsonProcessor m, Map map) throws Exception { String json = m.writeValueAsString(map); ContainerA objA = m.readValue(json, ContainerA.class);