Skip to content

Commit 81ec871

Browse files
committed
Allow TokenFilter to preserve empty
This creates two new method on `TokenFilter` which you can override to decide if empty arrays and objects should be included or excluded. An override like this, for example, will include all arrays and objects that were sent empty but strip any arrays or objects that were *filtered* to be empty: ``` @OverRide public boolean includeEmptyArray(boolean contentsFiltered) { return !contentsFiltered; } @OverRide public boolean includeEmptyObject(boolean contentsFiltered) { return !contentsFiltered; } ``` The default to preserve backwards compatibility is to always *exclude* empty objects. Closes FasterXML#715
1 parent 4465e7a commit 81ec871

File tree

6 files changed

+454
-5
lines changed

6 files changed

+454
-5
lines changed

src/main/java/com/fasterxml/jackson/core/filter/FilteringParserDelegate.java

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,16 +273,22 @@ public JsonToken nextToken() throws IOException
273273
_exposedContext = null;
274274
if (ctxt.inArray()) {
275275
t = delegate.getCurrentToken();
276-
// Is this guaranteed to work without further checks?
277-
// if (t != JsonToken.START_ARRAY) {
278276
_currToken = t;
277+
if (_currToken == JsonToken.END_ARRAY) {
278+
_headContext = _headContext.getParent();
279+
_itemFilter = _headContext.getFilter();
280+
}
279281
return t;
280282
}
281283

282284
// 19-Jul-2021, tatu: [core#700]: following was commented out?!
283285
// Almost! Most likely still have the current token;
284286
// with the sole exception of FIELD_NAME
285287
t = delegate.currentToken();
288+
if (t == JsonToken.END_OBJECT) {
289+
_headContext = _headContext.getParent();
290+
_itemFilter = _headContext.getFilter();
291+
}
286292
if (t != JsonToken.FIELD_NAME) {
287293
_currToken = t;
288294
return t;
@@ -562,12 +568,15 @@ protected final JsonToken _nextToken2() throws IOException
562568
continue main_loop;
563569

564570
case ID_END_ARRAY:
565-
case ID_END_OBJECT:
566571
{
567572
boolean returnEnd = _headContext.isStartHandled();
568573
f = _headContext.getFilter();
569574
if ((f != null) && (f != TokenFilter.INCLUDE_ALL)) {
575+
boolean includeEmpty = f.includeEmptyArray(_headContext.hasCurrentIndex());
570576
f.filterFinishArray();
577+
if (includeEmpty) {
578+
return _nextBuffered(_headContext);
579+
}
571580
}
572581
_headContext = _headContext.getParent();
573582
_itemFilter = _headContext.getFilter();
@@ -576,6 +585,23 @@ protected final JsonToken _nextToken2() throws IOException
576585
}
577586
}
578587
continue main_loop;
588+
case ID_END_OBJECT:
589+
{
590+
boolean returnEnd = _headContext.isStartHandled();
591+
f = _headContext.getFilter();
592+
if ((f != null) && (f != TokenFilter.INCLUDE_ALL)) {
593+
boolean includeEmpty = f.includeEmptyArray(_headContext.hasCurrentName());
594+
f.filterFinishObject();
595+
if (includeEmpty) {
596+
return _nextBuffered(_headContext);
597+
} }
598+
_headContext = _headContext.getParent();
599+
_itemFilter = _headContext.getFilter();
600+
if (returnEnd) {
601+
return (_currToken = t);
602+
}
603+
}
604+
continue main_loop;
579605

580606
case ID_FIELD_NAME:
581607
{
@@ -708,13 +734,16 @@ protected final JsonToken _nextTokenWithBuffering(final TokenFilterContext buffR
708734
continue main_loop;
709735

710736
case ID_END_ARRAY:
711-
case ID_END_OBJECT:
712737
{
713738
// Unlike with other loops, here we know that content was NOT
714739
// included (won't get this far otherwise)
715740
f = _headContext.getFilter();
716741
if ((f != null) && (f != TokenFilter.INCLUDE_ALL)) {
742+
boolean includeEmpty = f.includeEmptyArray(_headContext.hasCurrentIndex());
717743
f.filterFinishArray();
744+
if (includeEmpty) {
745+
return _nextBuffered(buffRoot);
746+
}
718747
}
719748
boolean gotEnd = (_headContext == buffRoot);
720749
boolean returnEnd = gotEnd && _headContext.isStartHandled();
@@ -727,6 +756,33 @@ protected final JsonToken _nextTokenWithBuffering(final TokenFilterContext buffR
727756
}
728757
}
729758
continue main_loop;
759+
case ID_END_OBJECT:
760+
{
761+
// Unlike with other loops, here we know that content was NOT
762+
// included (won't get this far otherwise)
763+
f = _headContext.getFilter();
764+
if ((f != null) && (f != TokenFilter.INCLUDE_ALL)) {
765+
boolean includeEmpty = f.includeEmptyObject(_headContext.hasCurrentName());
766+
f.filterFinishObject();
767+
if (includeEmpty) {
768+
_headContext._currentName = _headContext._parent == null
769+
? null
770+
: _headContext._parent._currentName;
771+
_headContext._needToHandleName = false;
772+
return _nextBuffered(buffRoot);
773+
}
774+
}
775+
boolean gotEnd = (_headContext == buffRoot);
776+
boolean returnEnd = gotEnd && _headContext.isStartHandled();
777+
778+
_headContext = _headContext.getParent();
779+
_itemFilter = _headContext.getFilter();
780+
781+
if (returnEnd) {
782+
return t;
783+
}
784+
}
785+
continue main_loop;
730786

731787
case ID_FIELD_NAME:
732788
{

src/main/java/com/fasterxml/jackson/core/filter/TokenFilter.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,14 @@ public boolean includeEmbeddedValue(Object value) {
432432
return _includeScalar();
433433
}
434434

435+
public boolean includeEmptyArray(boolean contentsFiltered) {
436+
return false;
437+
}
438+
439+
public boolean includeEmptyObject(boolean contentsFiltered) {
440+
return false;
441+
}
442+
435443
/*
436444
/**********************************************************
437445
/* Overrides

src/main/java/com/fasterxml/jackson/core/filter/TokenFilterContext.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,16 @@ public TokenFilterContext closeArray(JsonGenerator gen) throws IOException
233233
{
234234
if (_startHandled) {
235235
gen.writeEndArray();
236+
} else {
237+
if ((_filter != null) && (_filter != TokenFilter.INCLUDE_ALL)) {
238+
if (_filter.includeEmptyArray(hasCurrentIndex())) {
239+
if (_parent != null) {
240+
_parent._writePath(gen);
241+
}
242+
gen.writeStartArray();
243+
gen.writeEndArray();
244+
}
245+
}
236246
}
237247
if ((_filter != null) && (_filter != TokenFilter.INCLUDE_ALL)) {
238248
_filter.filterFinishArray();
@@ -244,6 +254,16 @@ public TokenFilterContext closeObject(JsonGenerator gen) throws IOException
244254
{
245255
if (_startHandled) {
246256
gen.writeEndObject();
257+
} else {
258+
if ((_filter != null) && (_filter != TokenFilter.INCLUDE_ALL)) {
259+
if (_filter.includeEmptyObject(hasCurrentName())) {
260+
if (_parent != null) {
261+
_parent._writePath(gen);
262+
}
263+
gen.writeStartObject();
264+
gen.writeEndObject();
265+
}
266+
}
247267
}
248268
if ((_filter != null) && (_filter != TokenFilter.INCLUDE_ALL)) {
249269
_filter.filterFinishObject();

src/test/java/com/fasterxml/jackson/core/BaseTest.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,11 +451,15 @@ protected String readAndWrite(JsonFactory f, JsonParser p) throws IOException
451451
g.disable(JsonGenerator.Feature.AUTO_CLOSE_JSON_CONTENT);
452452
try {
453453
while (p.nextToken() != null) {
454+
System.err.println(p.currentToken() + " " + p.currentName() + " " + p.currentValue());
454455
g.copyCurrentEvent(p);
455456
}
456457
} catch (IOException e) {
457458
g.flush();
458-
fail("Unexpected problem during `readAndWrite`. Output so far: '"+sw+"'; problem: "+e);
459+
throw new AssertionError(
460+
"Unexpected problem during `readAndWrite`. Output so far: '" +
461+
sw + "'; problem: " + e.getMessage(),
462+
e);
459463
}
460464
p.close();
461465
g.close();

0 commit comments

Comments
 (0)