Skip to content

Commit

Permalink
Implement #3476
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed May 3, 2022
1 parent 1731403 commit 396af5f
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 23 deletions.
2 changes: 2 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Project: jackson-databind
JSON `null` values on reading
#3443: Do not strip generic type from `Class<C>` when resolving `JavaType`
(contributed by Jan J)
#3476: Implement `JsonNodeFeature.WRITE_NULL_PROPERTIES` to allow skipping
JSON `null` values on writing

2.13.3 (not yet released)

Expand Down
73 changes: 51 additions & 22 deletions src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.JsonNodeFeature;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.util.RawValue;

Expand Down Expand Up @@ -302,59 +303,87 @@ public List<JsonNode> findParents(String propertyName, List<JsonNode> foundSoFar
* Method that can be called to serialize this node and
* all of its descendants using specified JSON generator.
*/
@SuppressWarnings("deprecation")
@Override
public void serialize(JsonGenerator g, SerializerProvider provider)
throws IOException
{
@SuppressWarnings("deprecation")
boolean trimEmptyArray = (provider != null) &&
!provider.isEnabled(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS);
if (provider != null) {
boolean trimEmptyArray = !provider.isEnabled(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS);
boolean skipNulls = !provider.isEnabled(JsonNodeFeature.WRITE_NULL_PROPERTIES);
if (trimEmptyArray || skipNulls) {
g.writeStartObject(this);
serializeFilteredContents(g, provider, trimEmptyArray, skipNulls);
g.writeEndObject();
return;
}
}
g.writeStartObject(this);
for (Map.Entry<String, JsonNode> en : _children.entrySet()) {
/* 17-Feb-2009, tatu: Can we trust that all nodes will always
* extend BaseJsonNode? Or if not, at least implement
* JsonSerializable? Let's start with former, change if
* we must.
*/
BaseJsonNode value = (BaseJsonNode) en.getValue();

// as per [databind#867], see if WRITE_EMPTY_JSON_ARRAYS feature is disabled,
// if the feature is disabled, then should not write an empty array
// to the output, so continue to the next element in the iteration
if (trimEmptyArray && value.isArray() && value.isEmpty(provider)) {
continue;
}
JsonNode value = en.getValue();
g.writeFieldName(en.getKey());
value.serialize(g, provider);
}
g.writeEndObject();
}

@SuppressWarnings("deprecation")
@Override
public void serializeWithType(JsonGenerator g, SerializerProvider provider,
TypeSerializer typeSer)
throws IOException
{
@SuppressWarnings("deprecation")
boolean trimEmptyArray = (provider != null) &&
!provider.isEnabled(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS);
boolean trimEmptyArray = false;
boolean skipNulls = false;
if (provider != null) {
trimEmptyArray = !provider.isEnabled(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS);
skipNulls = !provider.isEnabled(JsonNodeFeature.WRITE_NULL_PROPERTIES);
}

WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
typeSer.typeId(this, JsonToken.START_OBJECT));

if (trimEmptyArray || skipNulls) {
serializeFilteredContents(g, provider, trimEmptyArray, skipNulls);
} else {
for (Map.Entry<String, JsonNode> en : _children.entrySet()) {
JsonNode value = en.getValue();
g.writeFieldName(en.getKey());
value.serialize(g, provider);
}
}
typeSer.writeTypeSuffix(g, typeIdDef);
}

/**
* Helper method shared and called by {@link #serialize} and {@link #serializeWithType}
* in cases where actual filtering is needed based on configuration.
*
* @since 2.14
*/
protected void serializeFilteredContents(final JsonGenerator g, final SerializerProvider provider,
final boolean trimEmptyArray, final boolean skipNulls)
throws IOException
{
for (Map.Entry<String, JsonNode> en : _children.entrySet()) {
// 17-Feb-2009, tatu: Can we trust that all nodes will always
// extend BaseJsonNode? Or if not, at least implement
// JsonSerializable? Let's start with former, change if
// we must.
BaseJsonNode value = (BaseJsonNode) en.getValue();

// check if WRITE_EMPTY_JSON_ARRAYS feature is disabled,
// as per [databind#867], see if WRITE_EMPTY_JSON_ARRAYS feature is disabled,
// if the feature is disabled, then should not write an empty array
// to the output, so continue to the next element in the iteration
if (trimEmptyArray && value.isArray() && value.isEmpty(provider)) {
continue;
}
if (skipNulls && value.isNull()) {
continue;
}

g.writeFieldName(en.getKey());
value.serialize(g, provider);
}
typeSer.writeTypeSuffix(g, typeIdDef);
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,34 @@ public class NodeFeaturesTest extends BaseMapTest
{
private final ObjectMapper MAPPER = newJsonMapper();
private final ObjectReader READER = MAPPER.reader();
private final ObjectWriter WRITER = MAPPER.writer();

private final ObjectNode DOC_EMPTY = MAPPER.createObjectNode();
private final ObjectNode DOC_WITH_NULL = MAPPER.createObjectNode();
{
DOC_WITH_NULL.putNull("nvl");
}
private final String JSON_EMPTY = ("{}");
private final String JSON_WITH_NULL = a2q("{'nvl':null}");

public void testDefaultSettings() throws Exception
{
assertTrue(READER.isEnabled(JsonNodeFeature.READ_NULL_PROPERTIES));

assertFalse(READER.without(JsonNodeFeature.READ_NULL_PROPERTIES)
.isEnabled(JsonNodeFeature.READ_NULL_PROPERTIES));

assertTrue(READER.isEnabled(JsonNodeFeature.WRITE_NULL_PROPERTIES));
assertFalse(READER.without(JsonNodeFeature.WRITE_NULL_PROPERTIES)
.isEnabled(JsonNodeFeature.WRITE_NULL_PROPERTIES));
}

/*
/**********************************************************************
/* ObjectNode property handling
/**********************************************************************
*/

// [databind#3421]
public void testReadNulls() throws Exception
{
// so by default we'll get null included
Expand All @@ -47,4 +59,35 @@ public void testReadNulls() throws Exception
exp.put("c", true);
assertEquals(exp, r.readTree(a2q("{'a':1,'b':null,'c':true}")));
}

// [databind#3476]
public void testWriteNulls() throws Exception
{
// so by default we'll get null written
assertEquals(JSON_WITH_NULL, WRITER.writeValueAsString(DOC_WITH_NULL));

ObjectMapper noNullsMapper = JsonMapper.builder()
.disable(JsonNodeFeature.WRITE_NULL_PROPERTIES)
.build();
ObjectWriter w = noNullsMapper.writer();
assertFalse(w.isEnabled(JsonNodeFeature.WRITE_NULL_PROPERTIES));
assertEquals(JSON_EMPTY, w.writeValueAsString(DOC_WITH_NULL));

// but also verify we can "reset" writer's behavior
ObjectWriter w2 = w.with(JsonNodeFeature.WRITE_NULL_PROPERTIES);
assertEquals(JSON_WITH_NULL, w2.writeValueAsString(DOC_WITH_NULL));

// and then bit more complex doc
ObjectNode doc = noNullsMapper.createObjectNode();
doc.put("a", 1);
doc.putNull("b");
doc.put("c", true);
assertEquals(a2q("{'a':1,'c':true}"), w.writeValueAsString(doc));
}

/*
/**********************************************************************
/* Other features
/**********************************************************************
*/
}

0 comments on commit 396af5f

Please sign in to comment.