Skip to content

Commit 049992f

Browse files
authored
filter generator: create child object context when writing start object. fixes #890 (#891)
1 parent 287ec32 commit 049992f

File tree

3 files changed

+199
-39
lines changed

3 files changed

+199
-39
lines changed

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

+3
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ public void writeStartObject() throws IOException
313313

314314
TokenFilter f = _filterContext.checkValue(_itemFilter);
315315
if (f == null) {
316+
_filterContext = _filterContext.createChildObjectContext(null, false);
316317
return;
317318
}
318319

@@ -347,6 +348,7 @@ public void writeStartObject(Object forValue) throws IOException
347348

348349
TokenFilter f = _filterContext.checkValue(_itemFilter);
349350
if (f == null) {
351+
_filterContext = _filterContext.createChildObjectContext(null, false);
350352
return;
351353
}
352354

@@ -381,6 +383,7 @@ public void writeStartObject(Object forValue, int size) throws IOException
381383

382384
TokenFilter f = _filterContext.checkValue(_itemFilter);
383385
if (f == null) {
386+
_filterContext = _filterContext.createChildObjectContext(null, false);
384387
return;
385388
}
386389

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package com.fasterxml.jackson.core.filter;
2+
3+
import com.fasterxml.jackson.core.BaseTest;
4+
import com.fasterxml.jackson.core.JsonGenerator;
5+
import com.fasterxml.jackson.core.filter.TokenFilter.Inclusion;
6+
7+
import java.io.ByteArrayOutputStream;
8+
import java.io.IOException;
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
import java.util.Set;
12+
import java.util.function.UnaryOperator;
13+
import java.util.stream.Collectors;
14+
import java.util.stream.Stream;
15+
16+
// for [core#890]
17+
public class GeneratorFiltering890Test
18+
extends BaseTest
19+
{
20+
private static final class OrTokenFilter extends TokenFilter {
21+
22+
private final List<? extends TokenFilter> delegates;
23+
24+
private OrTokenFilter(final List<? extends TokenFilter> delegates) {
25+
this.delegates = delegates;
26+
}
27+
28+
static OrTokenFilter create(final Set<String> jsonPointers) {
29+
return new OrTokenFilter(jsonPointers.stream().map(JsonPointerBasedFilter::new).collect(Collectors.toList()));
30+
}
31+
32+
@Override
33+
public TokenFilter includeElement(final int index) {
34+
return executeDelegates(delegate -> delegate.includeElement(index));
35+
}
36+
37+
@Override
38+
public TokenFilter includeProperty(final String name) {
39+
return executeDelegates(delegate -> delegate.includeProperty(name));
40+
}
41+
42+
@Override
43+
public TokenFilter filterStartArray() {
44+
return this;
45+
}
46+
47+
@Override
48+
public TokenFilter filterStartObject() {
49+
return this;
50+
}
51+
52+
private TokenFilter executeDelegates(final UnaryOperator<TokenFilter> operator) {
53+
List<TokenFilter> nextDelegates = null;
54+
for (final TokenFilter delegate : delegates) {
55+
final TokenFilter next = operator.apply(delegate);
56+
if (null == next) {
57+
continue;
58+
}
59+
if (TokenFilter.INCLUDE_ALL == next) {
60+
return TokenFilter.INCLUDE_ALL;
61+
}
62+
63+
if (null == nextDelegates) {
64+
nextDelegates = new ArrayList<>(delegates.size());
65+
}
66+
nextDelegates.add(next);
67+
}
68+
return null == nextDelegates ? null : new OrTokenFilter(nextDelegates);
69+
}
70+
}
71+
72+
public void testIssue809_singleProperty() throws Exception
73+
{
74+
// GIVEN
75+
final Set<String> jsonPointers = Stream.of("/0/id").collect(Collectors.toSet());
76+
77+
// WHEN
78+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
79+
JsonGenerator g = new FilteringGeneratorDelegate(createGenerator(outputStream), OrTokenFilter.create(jsonPointers), Inclusion.INCLUDE_ALL_AND_PATH, true);
80+
81+
g.writeStartArray();
82+
writeOuterObject(g, 1, "first", "a", "second", "b");
83+
writeOuterObject(g, 2, "third", "c", "fourth", "d");
84+
g.writeEndArray();
85+
g.flush();
86+
g.close();
87+
outputStream.close();
88+
89+
// THEN
90+
String json = outputStream.toString("US-ASCII");
91+
assertEquals("[{\"id\":1}]", json);
92+
}
93+
94+
public void testIssue809_twoProperties() throws Exception
95+
{
96+
// GIVEN
97+
final Set<String> jsonPointers = Stream.of("/0/id", "/0/stuff/0/name").collect(Collectors.toSet());
98+
99+
// WHEN
100+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
101+
JsonGenerator g = new FilteringGeneratorDelegate(createGenerator(outputStream), OrTokenFilter.create(jsonPointers), Inclusion.INCLUDE_ALL_AND_PATH, true);
102+
103+
g.writeStartArray();
104+
writeOuterObject(g, 1, "first", "a", "second", "b");
105+
writeOuterObject(g, 2, "third", "c", "fourth", "d");
106+
g.writeEndArray();
107+
g.flush();
108+
g.close();
109+
outputStream.close();
110+
111+
// THEN
112+
String json = outputStream.toString("US-ASCII");
113+
assertEquals("[{\"id\":1,\"stuff\":[{\"name\":\"first\"}]}]", json);
114+
}
115+
116+
private static void writeOuterObject(final JsonGenerator g, final int id, final String name1, final String type1, final String name2, final String type2) throws IOException
117+
{
118+
g.writeStartObject();
119+
g.writeFieldName("id");
120+
g.writeNumber(id);
121+
g.writeFieldName("stuff");
122+
g.writeStartArray();
123+
writeInnerObject(g, name1, type1);
124+
writeInnerObject(g, name2, type2);
125+
g.writeEndArray();
126+
g.writeEndObject();
127+
}
128+
129+
private static void writeInnerObject(final JsonGenerator g, final String name, final String type) throws IOException
130+
{
131+
g.writeStartObject();
132+
g.writeFieldName("name");
133+
g.writeString(name);
134+
g.writeFieldName("type");
135+
g.writeString(type);
136+
g.writeEndObject();
137+
}
138+
}

src/test/java/com/fasterxml/jackson/core/filter/JsonPointerGeneratorFilteringTest.java

+58-39
Original file line numberDiff line numberDiff line change
@@ -14,90 +14,109 @@ public class JsonPointerGeneratorFilteringTest extends com.fasterxml.jackson.cor
1414

1515
public void testSimplePropertyWithPath() throws Exception
1616
{
17-
_assert(SIMPLE_INPUT, "/c", Inclusion.INCLUDE_ALL_AND_PATH, "{'c':{'d':{'a':true}}}");
18-
_assert(SIMPLE_INPUT, "/c/d", Inclusion.INCLUDE_ALL_AND_PATH, "{'c':{'d':{'a':true}}}");
19-
_assert(SIMPLE_INPUT, "/c/d/a", Inclusion.INCLUDE_ALL_AND_PATH, "{'c':{'d':{'a':true}}}");
17+
_assert(SIMPLE_INPUT, "/c", Inclusion.INCLUDE_ALL_AND_PATH, "{'c':{'d':{'a':true}}}", false);
18+
_assert(SIMPLE_INPUT, "/c/d", Inclusion.INCLUDE_ALL_AND_PATH, "{'c':{'d':{'a':true}}}", false);
19+
_assert(SIMPLE_INPUT, "/c/d/a", Inclusion.INCLUDE_ALL_AND_PATH, "{'c':{'d':{'a':true}}}", false);
2020

21-
_assert(SIMPLE_INPUT, "/c/d/a", Inclusion.INCLUDE_ALL_AND_PATH, "{'c':{'d':{'a':true}}}");
21+
_assert(SIMPLE_INPUT, "/c/d/a", Inclusion.INCLUDE_ALL_AND_PATH, "{'c':{'d':{'a':true}}}", false);
2222

23-
_assert(SIMPLE_INPUT, "/a", Inclusion.INCLUDE_ALL_AND_PATH, "{'a':1}");
24-
_assert(SIMPLE_INPUT, "/d", Inclusion.INCLUDE_ALL_AND_PATH, "{'d':null}");
23+
_assert(SIMPLE_INPUT, "/a", Inclusion.INCLUDE_ALL_AND_PATH, "{'a':1}", false);
24+
_assert(SIMPLE_INPUT, "/d", Inclusion.INCLUDE_ALL_AND_PATH, "{'d':null}", false);
2525

2626
// and then non-match
27-
_assert(SIMPLE_INPUT, "/x", Inclusion.INCLUDE_ALL_AND_PATH, "");
27+
_assert(SIMPLE_INPUT, "/x", Inclusion.INCLUDE_ALL_AND_PATH, "", false);
2828
}
2929

3030
public void testSimplePropertyWithoutPath() throws Exception
3131
{
32-
_assert(SIMPLE_INPUT, "/c", Inclusion.ONLY_INCLUDE_ALL, "{'d':{'a':true}}");
33-
_assert(SIMPLE_INPUT, "/c/d", Inclusion.ONLY_INCLUDE_ALL, "{'a':true}");
34-
_assert(SIMPLE_INPUT, "/c/d/a", Inclusion.ONLY_INCLUDE_ALL, "true");
32+
_assert(SIMPLE_INPUT, "/c", Inclusion.ONLY_INCLUDE_ALL, "{'d':{'a':true}}", false);
33+
_assert(SIMPLE_INPUT, "/c/d", Inclusion.ONLY_INCLUDE_ALL, "{'a':true}", false);
34+
_assert(SIMPLE_INPUT, "/c/d/a", Inclusion.ONLY_INCLUDE_ALL, "true", false);
3535

36-
_assert(SIMPLE_INPUT, "/a", Inclusion.ONLY_INCLUDE_ALL, "1");
37-
_assert(SIMPLE_INPUT, "/d", Inclusion.ONLY_INCLUDE_ALL, "null");
36+
_assert(SIMPLE_INPUT, "/a", Inclusion.ONLY_INCLUDE_ALL, "1", false);
37+
_assert(SIMPLE_INPUT, "/d", Inclusion.ONLY_INCLUDE_ALL, "null", false);
3838

3939
// and then non-match
40-
_assert(SIMPLE_INPUT, "/x", Inclusion.ONLY_INCLUDE_ALL, "");
40+
_assert(SIMPLE_INPUT, "/x", Inclusion.ONLY_INCLUDE_ALL, "", false);
4141
}
4242

4343
public void testArrayElementWithPath() throws Exception
4444
{
45-
_assert(SIMPLE_INPUT, "/b", Inclusion.INCLUDE_ALL_AND_PATH, "{'b':[1,2,3]}");
46-
_assert(SIMPLE_INPUT, "/b/1", Inclusion.INCLUDE_ALL_AND_PATH, "{'b':[2]}");
47-
_assert(SIMPLE_INPUT, "/b/2", Inclusion.INCLUDE_ALL_AND_PATH, "{'b':[3]}");
45+
_assert(SIMPLE_INPUT, "/b", Inclusion.INCLUDE_ALL_AND_PATH, "{'b':[1,2,3]}", false);
46+
_assert(SIMPLE_INPUT, "/b/1", Inclusion.INCLUDE_ALL_AND_PATH, "{'b':[2]}", false);
47+
_assert(SIMPLE_INPUT, "/b/2", Inclusion.INCLUDE_ALL_AND_PATH, "{'b':[3]}", false);
4848

4949
// and then non-match
50-
_assert(SIMPLE_INPUT, "/b/8", Inclusion.INCLUDE_ALL_AND_PATH, "");
50+
_assert(SIMPLE_INPUT, "/b/8", Inclusion.INCLUDE_ALL_AND_PATH, "", false);
5151
}
5252

5353
public void testArrayNestedWithPath() throws Exception
5454
{
55-
_assert("{'a':[true,{'b':3,'d':2},false]}", "/a/1/b", Inclusion.INCLUDE_ALL_AND_PATH, "{'a':[{'b':3}]}");
56-
_assert("[true,[1]]", "/0", Inclusion.INCLUDE_ALL_AND_PATH, "[true]");
57-
_assert("[true,[1]]", "/1", Inclusion.INCLUDE_ALL_AND_PATH, "[[1]]");
58-
_assert("[true,[1,2,[true],3],0]", "/0", Inclusion.INCLUDE_ALL_AND_PATH, "[true]");
59-
_assert("[true,[1,2,[true],3],0]", "/1", Inclusion.INCLUDE_ALL_AND_PATH, "[[1,2,[true],3]]");
60-
61-
_assert("[true,[1,2,[true],3],0]", "/1/2", Inclusion.INCLUDE_ALL_AND_PATH, "[[[true]]]");
62-
_assert("[true,[1,2,[true],3],0]", "/1/2/0", Inclusion.INCLUDE_ALL_AND_PATH, "[[[true]]]");
63-
_assert("[true,[1,2,[true],3],0]", "/1/3/0", Inclusion.INCLUDE_ALL_AND_PATH, "");
55+
_assert("{'a':[true,{'b':3,'d':2},false]}", "/a/1/b", Inclusion.INCLUDE_ALL_AND_PATH, "{'a':[{'b':3}]}", false);
56+
_assert("[true,[1]]", "/0", Inclusion.INCLUDE_ALL_AND_PATH, "[true]", false);
57+
_assert("[true,[1]]", "/1", Inclusion.INCLUDE_ALL_AND_PATH, "[[1]]", false);
58+
_assert("[true,[1,2,[true],3],0]", "/0", Inclusion.INCLUDE_ALL_AND_PATH, "[true]", false);
59+
_assert("[true,[1,2,[true],3],0]", "/1", Inclusion.INCLUDE_ALL_AND_PATH, "[[1,2,[true],3]]", false);
60+
61+
_assert("[true,[1,2,[true],3],0]", "/1/2", Inclusion.INCLUDE_ALL_AND_PATH, "[[[true]]]", false);
62+
_assert("[true,[1,2,[true],3],0]", "/1/2/0", Inclusion.INCLUDE_ALL_AND_PATH, "[[[true]]]", false);
63+
_assert("[true,[1,2,[true],3],0]", "/1/3/0", Inclusion.INCLUDE_ALL_AND_PATH, "", false);
6464
}
6565

6666
public void testArrayNestedWithoutPath() throws Exception
6767
{
68-
_assert("{'a':[true,{'b':3,'d':2},false]}", "/a/1/b", Inclusion.ONLY_INCLUDE_ALL, "3");
69-
_assert("[true,[1,2,[true],3],0]", "/0", Inclusion.ONLY_INCLUDE_ALL, "true");
68+
_assert("{'a':[true,{'b':3,'d':2},false]}", "/a/1/b", Inclusion.ONLY_INCLUDE_ALL, "3", false);
69+
_assert("[true,[1,2,[true],3],0]", "/0", Inclusion.ONLY_INCLUDE_ALL, "true", false);
7070
_assert("[true,[1,2,[true],3],0]", "/1", Inclusion.ONLY_INCLUDE_ALL,
71-
"[1,2,[true],3]");
71+
"[1,2,[true],3]", false);
7272

73-
_assert("[true,[1,2,[true],3],0]", "/1/2", Inclusion.ONLY_INCLUDE_ALL, "[true]");
74-
_assert("[true,[1,2,[true],3],0]", "/1/2/0", Inclusion.ONLY_INCLUDE_ALL, "true");
75-
_assert("[true,[1,2,[true],3],0]", "/1/3/0", Inclusion.ONLY_INCLUDE_ALL, "");
73+
_assert("[true,[1,2,[true],3],0]", "/1/2", Inclusion.ONLY_INCLUDE_ALL, "[true]", false);
74+
_assert("[true,[1,2,[true],3],0]", "/1/2/0", Inclusion.ONLY_INCLUDE_ALL, "true", false);
75+
_assert("[true,[1,2,[true],3],0]", "/1/3/0", Inclusion.ONLY_INCLUDE_ALL, "", false);
7676
}
7777

7878
// final String SIMPLE_INPUT = aposToQuotes("{'a':1,'b':[1,2,3],'c':{'d':{'a':true}},'d':null}");
7979

8080
public void testArrayElementWithoutPath() throws Exception
8181
{
82-
_assert(SIMPLE_INPUT, "/b", Inclusion.ONLY_INCLUDE_ALL, "[1,2,3]");
83-
_assert(SIMPLE_INPUT, "/b/1", Inclusion.ONLY_INCLUDE_ALL, "2");
84-
_assert(SIMPLE_INPUT, "/b/2", Inclusion.ONLY_INCLUDE_ALL, "3");
82+
_assert(SIMPLE_INPUT, "/b", Inclusion.ONLY_INCLUDE_ALL, "[1,2,3]", false);
83+
_assert(SIMPLE_INPUT, "/b/1", Inclusion.ONLY_INCLUDE_ALL, "2", false);
84+
_assert(SIMPLE_INPUT, "/b/2", Inclusion.ONLY_INCLUDE_ALL, "3", false);
8585

86-
_assert(SIMPLE_INPUT, "/b/8", Inclusion.ONLY_INCLUDE_ALL, "");
86+
_assert(SIMPLE_INPUT, "/b/8", Inclusion.ONLY_INCLUDE_ALL, "", false);
8787

8888
// and then non-match
89-
_assert(SIMPLE_INPUT, "/x", Inclusion.ONLY_INCLUDE_ALL, "");
89+
_assert(SIMPLE_INPUT, "/x", Inclusion.ONLY_INCLUDE_ALL, "", false);
9090
}
9191

92-
private void _assert(String input, String pathExpr, Inclusion tokenFilterInclusion, String exp)
92+
public void testAllowMultipleMatchesWithPath() throws Exception
93+
{
94+
_assert("[1,2,3]", "/0", Inclusion.INCLUDE_ALL_AND_PATH, "[1]", true);
95+
_assert("[1,2,3]", "/1", Inclusion.INCLUDE_ALL_AND_PATH, "[2]", true);
96+
_assert("[1,2,3]", "/2", Inclusion.INCLUDE_ALL_AND_PATH, "[3]", true);
97+
98+
_assert("{'a':[1,2,3]}", "/a/0", Inclusion.INCLUDE_ALL_AND_PATH, "{'a':[1]}", true);
99+
_assert("{'a':[1,2,3]}", "/a/1", Inclusion.INCLUDE_ALL_AND_PATH, "{'a':[2]}", true);
100+
_assert("{'a':[1,2,3]}", "/a/2", Inclusion.INCLUDE_ALL_AND_PATH, "{'a':[3]}", true);
101+
102+
_assert("[{'id':1},{'id':2},{'id':3}]", "/0/id", Inclusion.INCLUDE_ALL_AND_PATH, "[{'id':1}]", true);
103+
_assert("[{'id':1},{'id':2},{'id':3}]", "/1/id", Inclusion.INCLUDE_ALL_AND_PATH, "[{'id':2}]", true);
104+
_assert("[{'id':1},{'id':2},{'id':3}]", "/2/id", Inclusion.INCLUDE_ALL_AND_PATH, "[{'id':3}]", true);
105+
106+
_assert("[{'id':1,'stuff':[1,2,3]},{'id':2,'stuff':[4,5,6]},{'id':3,'stuff':[7,8,9]}]", "/0/stuff/0", Inclusion.INCLUDE_ALL_AND_PATH, "[{'stuff':[1]}]", true);
107+
_assert("[{'id':1,'stuff':[1,2,3]},{'id':2,'stuff':[4,5,6]},{'id':3,'stuff':[7,8,9]}]", "/1/stuff/1", Inclusion.INCLUDE_ALL_AND_PATH, "[{'stuff':[5]}]", true);
108+
_assert("[{'id':1,'stuff':[1,2,3]},{'id':2,'stuff':[4,5,6]},{'id':3,'stuff':[7,8,9]}]", "/2/stuff/2", Inclusion.INCLUDE_ALL_AND_PATH, "[{'stuff':[9]}]", true);
109+
}
110+
111+
private void _assert(String input, String pathExpr, Inclusion tokenFilterInclusion, String exp, boolean allowMultipleMatches)
93112
throws Exception
94113
{
95114
StringWriter w = new StringWriter();
96115

97116
JsonGenerator g0 = JSON_F.createGenerator(w);
98117
FilteringGeneratorDelegate g = new FilteringGeneratorDelegate(g0,
99118
new JsonPointerBasedFilter(pathExpr),
100-
tokenFilterInclusion, false);
119+
tokenFilterInclusion, allowMultipleMatches);
101120

102121
try {
103122
writeJsonDoc(JSON_F, input, g);

0 commit comments

Comments
 (0)