Skip to content

Commit 396af5f

Browse files
committed
Implement #3476
1 parent 1731403 commit 396af5f

File tree

3 files changed

+97
-23
lines changed

3 files changed

+97
-23
lines changed

release-notes/VERSION-2.x

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ Project: jackson-databind
1919
JSON `null` values on reading
2020
#3443: Do not strip generic type from `Class<C>` when resolving `JavaType`
2121
(contributed by Jan J)
22+
#3476: Implement `JsonNodeFeature.WRITE_NULL_PROPERTIES` to allow skipping
23+
JSON `null` values on writing
2224

2325
2.13.3 (not yet released)
2426

src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java

+51-22
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.fasterxml.jackson.core.*;
99
import com.fasterxml.jackson.core.type.WritableTypeId;
1010
import com.fasterxml.jackson.databind.*;
11+
import com.fasterxml.jackson.databind.cfg.JsonNodeFeature;
1112
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
1213
import com.fasterxml.jackson.databind.util.RawValue;
1314

@@ -302,59 +303,87 @@ public List<JsonNode> findParents(String propertyName, List<JsonNode> foundSoFar
302303
* Method that can be called to serialize this node and
303304
* all of its descendants using specified JSON generator.
304305
*/
306+
@SuppressWarnings("deprecation")
305307
@Override
306308
public void serialize(JsonGenerator g, SerializerProvider provider)
307309
throws IOException
308310
{
309-
@SuppressWarnings("deprecation")
310-
boolean trimEmptyArray = (provider != null) &&
311-
!provider.isEnabled(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS);
311+
if (provider != null) {
312+
boolean trimEmptyArray = !provider.isEnabled(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS);
313+
boolean skipNulls = !provider.isEnabled(JsonNodeFeature.WRITE_NULL_PROPERTIES);
314+
if (trimEmptyArray || skipNulls) {
315+
g.writeStartObject(this);
316+
serializeFilteredContents(g, provider, trimEmptyArray, skipNulls);
317+
g.writeEndObject();
318+
return;
319+
}
320+
}
312321
g.writeStartObject(this);
313322
for (Map.Entry<String, JsonNode> en : _children.entrySet()) {
314-
/* 17-Feb-2009, tatu: Can we trust that all nodes will always
315-
* extend BaseJsonNode? Or if not, at least implement
316-
* JsonSerializable? Let's start with former, change if
317-
* we must.
318-
*/
319-
BaseJsonNode value = (BaseJsonNode) en.getValue();
320-
321-
// as per [databind#867], see if WRITE_EMPTY_JSON_ARRAYS feature is disabled,
322-
// if the feature is disabled, then should not write an empty array
323-
// to the output, so continue to the next element in the iteration
324-
if (trimEmptyArray && value.isArray() && value.isEmpty(provider)) {
325-
continue;
326-
}
323+
JsonNode value = en.getValue();
327324
g.writeFieldName(en.getKey());
328325
value.serialize(g, provider);
329326
}
330327
g.writeEndObject();
331328
}
332329

330+
@SuppressWarnings("deprecation")
333331
@Override
334332
public void serializeWithType(JsonGenerator g, SerializerProvider provider,
335333
TypeSerializer typeSer)
336334
throws IOException
337335
{
338-
@SuppressWarnings("deprecation")
339-
boolean trimEmptyArray = (provider != null) &&
340-
!provider.isEnabled(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS);
336+
boolean trimEmptyArray = false;
337+
boolean skipNulls = false;
338+
if (provider != null) {
339+
trimEmptyArray = !provider.isEnabled(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS);
340+
skipNulls = !provider.isEnabled(JsonNodeFeature.WRITE_NULL_PROPERTIES);
341+
}
341342

342343
WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
343344
typeSer.typeId(this, JsonToken.START_OBJECT));
345+
346+
if (trimEmptyArray || skipNulls) {
347+
serializeFilteredContents(g, provider, trimEmptyArray, skipNulls);
348+
} else {
349+
for (Map.Entry<String, JsonNode> en : _children.entrySet()) {
350+
JsonNode value = en.getValue();
351+
g.writeFieldName(en.getKey());
352+
value.serialize(g, provider);
353+
}
354+
}
355+
typeSer.writeTypeSuffix(g, typeIdDef);
356+
}
357+
358+
/**
359+
* Helper method shared and called by {@link #serialize} and {@link #serializeWithType}
360+
* in cases where actual filtering is needed based on configuration.
361+
*
362+
* @since 2.14
363+
*/
364+
protected void serializeFilteredContents(final JsonGenerator g, final SerializerProvider provider,
365+
final boolean trimEmptyArray, final boolean skipNulls)
366+
throws IOException
367+
{
344368
for (Map.Entry<String, JsonNode> en : _children.entrySet()) {
369+
// 17-Feb-2009, tatu: Can we trust that all nodes will always
370+
// extend BaseJsonNode? Or if not, at least implement
371+
// JsonSerializable? Let's start with former, change if
372+
// we must.
345373
BaseJsonNode value = (BaseJsonNode) en.getValue();
346374

347-
// check if WRITE_EMPTY_JSON_ARRAYS feature is disabled,
375+
// as per [databind#867], see if WRITE_EMPTY_JSON_ARRAYS feature is disabled,
348376
// if the feature is disabled, then should not write an empty array
349377
// to the output, so continue to the next element in the iteration
350378
if (trimEmptyArray && value.isArray() && value.isEmpty(provider)) {
379+
continue;
380+
}
381+
if (skipNulls && value.isNull()) {
351382
continue;
352383
}
353-
354384
g.writeFieldName(en.getKey());
355385
value.serialize(g, provider);
356386
}
357-
typeSer.writeTypeSuffix(g, typeIdDef);
358387
}
359388

360389
/*

src/test/java/com/fasterxml/jackson/databind/node/NodeFeaturesTest.java

+44-1
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,34 @@ public class NodeFeaturesTest extends BaseMapTest
99
{
1010
private final ObjectMapper MAPPER = newJsonMapper();
1111
private final ObjectReader READER = MAPPER.reader();
12+
private final ObjectWriter WRITER = MAPPER.writer();
1213

1314
private final ObjectNode DOC_EMPTY = MAPPER.createObjectNode();
1415
private final ObjectNode DOC_WITH_NULL = MAPPER.createObjectNode();
1516
{
1617
DOC_WITH_NULL.putNull("nvl");
1718
}
19+
private final String JSON_EMPTY = ("{}");
1820
private final String JSON_WITH_NULL = a2q("{'nvl':null}");
1921

2022
public void testDefaultSettings() throws Exception
2123
{
2224
assertTrue(READER.isEnabled(JsonNodeFeature.READ_NULL_PROPERTIES));
23-
2425
assertFalse(READER.without(JsonNodeFeature.READ_NULL_PROPERTIES)
2526
.isEnabled(JsonNodeFeature.READ_NULL_PROPERTIES));
27+
28+
assertTrue(READER.isEnabled(JsonNodeFeature.WRITE_NULL_PROPERTIES));
29+
assertFalse(READER.without(JsonNodeFeature.WRITE_NULL_PROPERTIES)
30+
.isEnabled(JsonNodeFeature.WRITE_NULL_PROPERTIES));
2631
}
2732

33+
/*
34+
/**********************************************************************
35+
/* ObjectNode property handling
36+
/**********************************************************************
37+
*/
38+
39+
// [databind#3421]
2840
public void testReadNulls() throws Exception
2941
{
3042
// so by default we'll get null included
@@ -47,4 +59,35 @@ public void testReadNulls() throws Exception
4759
exp.put("c", true);
4860
assertEquals(exp, r.readTree(a2q("{'a':1,'b':null,'c':true}")));
4961
}
62+
63+
// [databind#3476]
64+
public void testWriteNulls() throws Exception
65+
{
66+
// so by default we'll get null written
67+
assertEquals(JSON_WITH_NULL, WRITER.writeValueAsString(DOC_WITH_NULL));
68+
69+
ObjectMapper noNullsMapper = JsonMapper.builder()
70+
.disable(JsonNodeFeature.WRITE_NULL_PROPERTIES)
71+
.build();
72+
ObjectWriter w = noNullsMapper.writer();
73+
assertFalse(w.isEnabled(JsonNodeFeature.WRITE_NULL_PROPERTIES));
74+
assertEquals(JSON_EMPTY, w.writeValueAsString(DOC_WITH_NULL));
75+
76+
// but also verify we can "reset" writer's behavior
77+
ObjectWriter w2 = w.with(JsonNodeFeature.WRITE_NULL_PROPERTIES);
78+
assertEquals(JSON_WITH_NULL, w2.writeValueAsString(DOC_WITH_NULL));
79+
80+
// and then bit more complex doc
81+
ObjectNode doc = noNullsMapper.createObjectNode();
82+
doc.put("a", 1);
83+
doc.putNull("b");
84+
doc.put("c", true);
85+
assertEquals(a2q("{'a':1,'c':true}"), w.writeValueAsString(doc));
86+
}
87+
88+
/*
89+
/**********************************************************************
90+
/* Other features
91+
/**********************************************************************
92+
*/
5093
}

0 commit comments

Comments
 (0)