Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@ public class JsonNullableDeserializer extends ReferenceTypeDeserializer<JsonNull
private static final long serialVersionUID = 1L;

private boolean isStringDeserializer = false;
private final boolean mapBlankStringToNull;

/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
public JsonNullableDeserializer(JavaType fullType, ValueInstantiator inst,
TypeDeserializer typeDeser, JsonDeserializer<?> deser) {
TypeDeserializer typeDeser, JsonDeserializer<?> deser,
boolean mapBlankStringToNull) {
super(fullType, inst, typeDeser, deser);
this.mapBlankStringToNull = mapBlankStringToNull;
if (fullType instanceof ReferenceType && ((ReferenceType) fullType).getReferencedType() != null) {
this.isStringDeserializer = ((ReferenceType) fullType).getReferencedType().isTypeOrSubTypeOf(String.class);
}
Expand All @@ -45,7 +48,7 @@ public JsonNullable<Object> deserialize(JsonParser p, DeserializationContext ctx
if (t == JsonToken.VALUE_STRING && !isStringDeserializer) {
String str = p.getText().trim();
if (str.isEmpty()) {
return JsonNullable.undefined();
return mapBlankStringToNull ? JsonNullable.of(null) : JsonNullable.undefined();
}
}
return super.deserialize(p, ctxt);
Expand All @@ -54,7 +57,7 @@ public JsonNullable<Object> deserialize(JsonParser p, DeserializationContext ctx
@Override
public JsonNullableDeserializer withResolved(TypeDeserializer typeDeser, JsonDeserializer<?> valueDeser) {
return new JsonNullableDeserializer(_fullType, _valueInstantiator,
typeDeser, valueDeser);
typeDeser, valueDeser, mapBlankStringToNull);
}

@Override
Expand Down Expand Up @@ -92,4 +95,4 @@ public Boolean supportsUpdate(DeserializationConfig config) {
// yes; regardless of value deserializer reference itself may be updated
return Boolean.TRUE;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,18 @@

public class JsonNullableDeserializers extends Deserializers.Base {

private final boolean mapBlankStringToNull;

public JsonNullableDeserializers(boolean mapBlankStringToNull) {
this.mapBlankStringToNull = mapBlankStringToNull;
}

@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 JsonNullableDeserializer(refType, null, contentTypeDeserializer, contentDeserializer, mapBlankStringToNull)
: null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,27 @@
public class JsonNullableModule extends Module {

private final String NAME = "JsonNullableModule";
private boolean mapBlankStringToNull = false;

/**
* Configures whether blank strings (e.g. {@code ""}, {@code " "}) deserialized into
* non-String {@code JsonNullable} fields are mapped to {@code JsonNullable.of(null)}
* instead of {@code JsonNullable.undefined()}.
*
* <p>This is relevant for PATCH semantics: a blank string sent by a client expresses
* explicit intent to clear a value, which {@code undefined()} silently swallows.
*
* <p>Default is {@code false} for backwards compatibility.
*/
public JsonNullableModule mapBlankStringToNull(boolean state) {
this.mapBlankStringToNull = state;
return this;
}

@Override
public void setupModule(SetupContext context) {
context.addSerializers(new JsonNullableSerializers());
context.addDeserializers(new JsonNullableDeserializers());
context.addDeserializers(new JsonNullableDeserializers(mapBlankStringToNull));
// Modify type info for JsonNullable
context.addTypeModifier(new JsonNullableTypeModifier());
context.addBeanSerializerModifier(new JsonNullableBeanSerializerModifier());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

class JsonNullWithEmptyTest extends ModuleTestBase
{
private final ObjectMapper MAPPER = mapperWithModule();
private final ObjectMapper MAPPER_BLANK_TO_NULL = mapperWithModule(new JsonNullableModule().mapBlankStringToNull(true));

static class BooleanBean {
public JsonNullable<Boolean> value;
Expand All @@ -20,6 +23,8 @@ public BooleanBean(Boolean b) {
}
}

// default behavior (mapBlankStringToNull = false)

@Test
void testJsonNullableFromEmpty() throws Exception {
JsonNullable<?> value = MAPPER.readValue(quote(""), new TypeReference<JsonNullable<Integer>>() {});
Expand All @@ -37,4 +42,27 @@ void testBooleanWithEmpty() throws Exception {
assertFalse(b.value.isPresent());
}

}
// mapBlankStringToNull = true

@Test
void testJsonNullableFromEmptyWithMapBlankStringToNull() throws Exception {
JsonNullable<?> value = MAPPER_BLANK_TO_NULL.readValue(quote(""), new TypeReference<JsonNullable<Integer>>() {});
assertTrue(value.isPresent());
assertNull(value.get());
}

@Test
void testJsonNullableFromBlankWithMapBlankStringToNull() throws Exception {
JsonNullable<?> value = MAPPER_BLANK_TO_NULL.readValue(quote(" "), new TypeReference<JsonNullable<Integer>>() {});
assertTrue(value.isPresent());
assertNull(value.get());
}

@Test
void testBooleanWithEmptyWithMapBlankStringToNull() throws Exception {
BooleanBean b = MAPPER_BLANK_TO_NULL.readValue(aposToQuotes("{'value':''}"), BooleanBean.class);
assertNotNull(b.value);
assertTrue(b.value.isPresent());
assertNull(b.value.get());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public static class ContainedImpl implements Contained { }
*/

private final ObjectMapper MAPPER = mapperWithModule();
private final ObjectMapper MAPPER_BLANK_TO_NULL = mapperWithModule(new JsonNullableModule().mapBlankStringToNull(true));

@Test
void testJsonNullableTypeResolution() {
Expand Down Expand Up @@ -270,11 +271,18 @@ void testJsonNullableCollection() throws Exception {
}

@Test
void testDeserNull() throws Exception {
void testDeserEmptyStringForNonStringType() throws Exception {
JsonNullable<?> value = MAPPER.readValue("\"\"", new TypeReference<JsonNullable<Integer>>() {});
assertFalse(value.isPresent());
}

@Test
void testDeserEmptyStringForNonStringTypeWithMapBlankStringToNull() throws Exception {
JsonNullable<?> value = MAPPER_BLANK_TO_NULL.readValue("\"\"", new TypeReference<JsonNullable<Integer>>() {});
assertTrue(value.isPresent());
assertNull(value.get());
}

@Test
void testPolymorphic() throws Exception {
final Container dto = new Container();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ protected ObjectMapper mapperWithModule()
return mapper;
}

protected ObjectMapper mapperWithModule(JsonNullableModule module)
{
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
return mapper;
}

/*
/**********************************************************************
/* Helper methods, setup
Expand All @@ -47,4 +54,4 @@ protected String quote(String str) {
protected String aposToQuotes(String json) {
return json.replace("'", "\"");
}
}
}