Skip to content

Commit

Permalink
filter generator: create child object context when writing start obje…
Browse files Browse the repository at this point in the history
…ct. fixes #890 (#894)
  • Loading branch information
DemonicTutor committed Jan 17, 2023
1 parent 928a0f1 commit 20ef692
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ public void writeStartObject() throws IOException

TokenFilter f = _filterContext.checkValue(_itemFilter);
if (f == null) {
_filterContext = _filterContext.createChildObjectContext(null, false);
return;
}

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

TokenFilter f = _filterContext.checkValue(_itemFilter);
if (f == null) {
_filterContext = _filterContext.createChildObjectContext(null, false);
return;
}

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

TokenFilter f = _filterContext.checkValue(_itemFilter);
if (f == null) {
_filterContext = _filterContext.createChildObjectContext(null, false);
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package com.fasterxml.jackson.core.filter;

import com.fasterxml.jackson.core.BaseTest;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.filter.TokenFilter.Inclusion;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;

// for [core#890]
public class GeneratorFiltering890Test
extends BaseTest
{
private static final class OrTokenFilter extends TokenFilter {

private final List<? extends TokenFilter> delegates;

private OrTokenFilter(final List<? extends TokenFilter> delegates) {
this.delegates = delegates;
}

static OrTokenFilter create(final Set<String> jsonPointers) {
return new OrTokenFilter(jsonPointers.stream().map(JsonPointerBasedFilter::new).collect(Collectors.toList()));
}

@Override
public TokenFilter includeElement(final int index) {
return executeDelegates(delegate -> delegate.includeElement(index));
}

@Override
public TokenFilter includeProperty(final String name) {
return executeDelegates(delegate -> delegate.includeProperty(name));
}

@Override
public TokenFilter filterStartArray() {
return this;
}

@Override
public TokenFilter filterStartObject() {
return this;
}

private TokenFilter executeDelegates(final UnaryOperator<TokenFilter> operator) {
List<TokenFilter> nextDelegates = null;
for (final TokenFilter delegate : delegates) {
final TokenFilter next = operator.apply(delegate);
if (null == next) {
continue;
}
if (TokenFilter.INCLUDE_ALL == next) {
return TokenFilter.INCLUDE_ALL;
}

if (null == nextDelegates) {
nextDelegates = new ArrayList<>(delegates.size());
}
nextDelegates.add(next);
}
return null == nextDelegates ? null : new OrTokenFilter(nextDelegates);
}
}

public void testIssue809_singleProperty() throws Exception
{
// GIVEN
final Set<String> jsonPointers = Stream.of("/0/id").collect(Collectors.toSet());

// WHEN
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
JsonGenerator g = new FilteringGeneratorDelegate(createGenerator(outputStream), OrTokenFilter.create(jsonPointers), Inclusion.INCLUDE_ALL_AND_PATH, true);

g.writeStartArray();
writeOuterObject(g, 1, "first", "a", "second", "b");
writeOuterObject(g, 2, "third", "c", "fourth", "d");
g.writeEndArray();
g.flush();
g.close();
outputStream.close();

// THEN
String json = outputStream.toString("US-ASCII");
assertEquals("[{\"id\":1}]", json);
}

public void testIssue809_twoProperties() throws Exception
{
// GIVEN
final Set<String> jsonPointers = Stream.of("/0/id", "/0/stuff/0/name").collect(Collectors.toSet());

// WHEN
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
JsonGenerator g = new FilteringGeneratorDelegate(createGenerator(outputStream), OrTokenFilter.create(jsonPointers), Inclusion.INCLUDE_ALL_AND_PATH, true);

g.writeStartArray();
writeOuterObject(g, 1, "first", "a", "second", "b");
writeOuterObject(g, 2, "third", "c", "fourth", "d");
g.writeEndArray();
g.flush();
g.close();
outputStream.close();

// THEN
String json = outputStream.toString("US-ASCII");
assertEquals("[{\"id\":1,\"stuff\":[{\"name\":\"first\"}]}]", json);
}

private static void writeOuterObject(final JsonGenerator g, final int id, final String name1, final String type1, final String name2, final String type2) throws IOException
{
g.writeStartObject();
g.writeFieldName("id");
g.writeNumber(id);
g.writeFieldName("stuff");
g.writeStartArray();
writeInnerObject(g, name1, type1);
writeInnerObject(g, name2, type2);
g.writeEndArray();
g.writeEndObject();
}

private static void writeInnerObject(final JsonGenerator g, final String name, final String type) throws IOException
{
g.writeStartObject();
g.writeFieldName("name");
g.writeString(name);
g.writeFieldName("type");
g.writeString(type);
g.writeEndObject();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,90 +14,109 @@ public class JsonPointerGeneratorFilteringTest extends com.fasterxml.jackson.cor

public void testSimplePropertyWithPath() throws Exception
{
_assert(SIMPLE_INPUT, "/c", Inclusion.INCLUDE_ALL_AND_PATH, "{'c':{'d':{'a':true}}}");
_assert(SIMPLE_INPUT, "/c/d", Inclusion.INCLUDE_ALL_AND_PATH, "{'c':{'d':{'a':true}}}");
_assert(SIMPLE_INPUT, "/c/d/a", Inclusion.INCLUDE_ALL_AND_PATH, "{'c':{'d':{'a':true}}}");
_assert(SIMPLE_INPUT, "/c", Inclusion.INCLUDE_ALL_AND_PATH, "{'c':{'d':{'a':true}}}", false);
_assert(SIMPLE_INPUT, "/c/d", Inclusion.INCLUDE_ALL_AND_PATH, "{'c':{'d':{'a':true}}}", false);
_assert(SIMPLE_INPUT, "/c/d/a", Inclusion.INCLUDE_ALL_AND_PATH, "{'c':{'d':{'a':true}}}", false);

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

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

// and then non-match
_assert(SIMPLE_INPUT, "/x", Inclusion.INCLUDE_ALL_AND_PATH, "");
_assert(SIMPLE_INPUT, "/x", Inclusion.INCLUDE_ALL_AND_PATH, "", false);
}

public void testSimplePropertyWithoutPath() throws Exception
{
_assert(SIMPLE_INPUT, "/c", Inclusion.ONLY_INCLUDE_ALL, "{'d':{'a':true}}");
_assert(SIMPLE_INPUT, "/c/d", Inclusion.ONLY_INCLUDE_ALL, "{'a':true}");
_assert(SIMPLE_INPUT, "/c/d/a", Inclusion.ONLY_INCLUDE_ALL, "true");
_assert(SIMPLE_INPUT, "/c", Inclusion.ONLY_INCLUDE_ALL, "{'d':{'a':true}}", false);
_assert(SIMPLE_INPUT, "/c/d", Inclusion.ONLY_INCLUDE_ALL, "{'a':true}", false);
_assert(SIMPLE_INPUT, "/c/d/a", Inclusion.ONLY_INCLUDE_ALL, "true", false);

_assert(SIMPLE_INPUT, "/a", Inclusion.ONLY_INCLUDE_ALL, "1");
_assert(SIMPLE_INPUT, "/d", Inclusion.ONLY_INCLUDE_ALL, "null");
_assert(SIMPLE_INPUT, "/a", Inclusion.ONLY_INCLUDE_ALL, "1", false);
_assert(SIMPLE_INPUT, "/d", Inclusion.ONLY_INCLUDE_ALL, "null", false);

// and then non-match
_assert(SIMPLE_INPUT, "/x", Inclusion.ONLY_INCLUDE_ALL, "");
_assert(SIMPLE_INPUT, "/x", Inclusion.ONLY_INCLUDE_ALL, "", false);
}

public void testArrayElementWithPath() throws Exception
{
_assert(SIMPLE_INPUT, "/b", Inclusion.INCLUDE_ALL_AND_PATH, "{'b':[1,2,3]}");
_assert(SIMPLE_INPUT, "/b/1", Inclusion.INCLUDE_ALL_AND_PATH, "{'b':[2]}");
_assert(SIMPLE_INPUT, "/b/2", Inclusion.INCLUDE_ALL_AND_PATH, "{'b':[3]}");
_assert(SIMPLE_INPUT, "/b", Inclusion.INCLUDE_ALL_AND_PATH, "{'b':[1,2,3]}", false);
_assert(SIMPLE_INPUT, "/b/1", Inclusion.INCLUDE_ALL_AND_PATH, "{'b':[2]}", false);
_assert(SIMPLE_INPUT, "/b/2", Inclusion.INCLUDE_ALL_AND_PATH, "{'b':[3]}", false);

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

public void testArrayNestedWithPath() throws Exception
{
_assert("{'a':[true,{'b':3,'d':2},false]}", "/a/1/b", Inclusion.INCLUDE_ALL_AND_PATH, "{'a':[{'b':3}]}");
_assert("[true,[1]]", "/0", Inclusion.INCLUDE_ALL_AND_PATH, "[true]");
_assert("[true,[1]]", "/1", Inclusion.INCLUDE_ALL_AND_PATH, "[[1]]");
_assert("[true,[1,2,[true],3],0]", "/0", Inclusion.INCLUDE_ALL_AND_PATH, "[true]");
_assert("[true,[1,2,[true],3],0]", "/1", Inclusion.INCLUDE_ALL_AND_PATH, "[[1,2,[true],3]]");

_assert("[true,[1,2,[true],3],0]", "/1/2", Inclusion.INCLUDE_ALL_AND_PATH, "[[[true]]]");
_assert("[true,[1,2,[true],3],0]", "/1/2/0", Inclusion.INCLUDE_ALL_AND_PATH, "[[[true]]]");
_assert("[true,[1,2,[true],3],0]", "/1/3/0", Inclusion.INCLUDE_ALL_AND_PATH, "");
_assert("{'a':[true,{'b':3,'d':2},false]}", "/a/1/b", Inclusion.INCLUDE_ALL_AND_PATH, "{'a':[{'b':3}]}", false);
_assert("[true,[1]]", "/0", Inclusion.INCLUDE_ALL_AND_PATH, "[true]", false);
_assert("[true,[1]]", "/1", Inclusion.INCLUDE_ALL_AND_PATH, "[[1]]", false);
_assert("[true,[1,2,[true],3],0]", "/0", Inclusion.INCLUDE_ALL_AND_PATH, "[true]", false);
_assert("[true,[1,2,[true],3],0]", "/1", Inclusion.INCLUDE_ALL_AND_PATH, "[[1,2,[true],3]]", false);

_assert("[true,[1,2,[true],3],0]", "/1/2", Inclusion.INCLUDE_ALL_AND_PATH, "[[[true]]]", false);
_assert("[true,[1,2,[true],3],0]", "/1/2/0", Inclusion.INCLUDE_ALL_AND_PATH, "[[[true]]]", false);
_assert("[true,[1,2,[true],3],0]", "/1/3/0", Inclusion.INCLUDE_ALL_AND_PATH, "", false);
}

public void testArrayNestedWithoutPath() throws Exception
{
_assert("{'a':[true,{'b':3,'d':2},false]}", "/a/1/b", Inclusion.ONLY_INCLUDE_ALL, "3");
_assert("[true,[1,2,[true],3],0]", "/0", Inclusion.ONLY_INCLUDE_ALL, "true");
_assert("{'a':[true,{'b':3,'d':2},false]}", "/a/1/b", Inclusion.ONLY_INCLUDE_ALL, "3", false);
_assert("[true,[1,2,[true],3],0]", "/0", Inclusion.ONLY_INCLUDE_ALL, "true", false);
_assert("[true,[1,2,[true],3],0]", "/1", Inclusion.ONLY_INCLUDE_ALL,
"[1,2,[true],3]");
"[1,2,[true],3]", false);

_assert("[true,[1,2,[true],3],0]", "/1/2", Inclusion.ONLY_INCLUDE_ALL, "[true]");
_assert("[true,[1,2,[true],3],0]", "/1/2/0", Inclusion.ONLY_INCLUDE_ALL, "true");
_assert("[true,[1,2,[true],3],0]", "/1/3/0", Inclusion.ONLY_INCLUDE_ALL, "");
_assert("[true,[1,2,[true],3],0]", "/1/2", Inclusion.ONLY_INCLUDE_ALL, "[true]", false);
_assert("[true,[1,2,[true],3],0]", "/1/2/0", Inclusion.ONLY_INCLUDE_ALL, "true", false);
_assert("[true,[1,2,[true],3],0]", "/1/3/0", Inclusion.ONLY_INCLUDE_ALL, "", false);
}

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

public void testArrayElementWithoutPath() throws Exception
{
_assert(SIMPLE_INPUT, "/b", Inclusion.ONLY_INCLUDE_ALL, "[1,2,3]");
_assert(SIMPLE_INPUT, "/b/1", Inclusion.ONLY_INCLUDE_ALL, "2");
_assert(SIMPLE_INPUT, "/b/2", Inclusion.ONLY_INCLUDE_ALL, "3");
_assert(SIMPLE_INPUT, "/b", Inclusion.ONLY_INCLUDE_ALL, "[1,2,3]", false);
_assert(SIMPLE_INPUT, "/b/1", Inclusion.ONLY_INCLUDE_ALL, "2", false);
_assert(SIMPLE_INPUT, "/b/2", Inclusion.ONLY_INCLUDE_ALL, "3", false);

_assert(SIMPLE_INPUT, "/b/8", Inclusion.ONLY_INCLUDE_ALL, "");
_assert(SIMPLE_INPUT, "/b/8", Inclusion.ONLY_INCLUDE_ALL, "", false);

// and then non-match
_assert(SIMPLE_INPUT, "/x", Inclusion.ONLY_INCLUDE_ALL, "");
_assert(SIMPLE_INPUT, "/x", Inclusion.ONLY_INCLUDE_ALL, "", false);
}

private void _assert(String input, String pathExpr, Inclusion tokenFilterInclusion, String exp)
public void testAllowMultipleMatchesWithPath() throws Exception
{
_assert("[1,2,3]", "/0", Inclusion.INCLUDE_ALL_AND_PATH, "[1]", true);
_assert("[1,2,3]", "/1", Inclusion.INCLUDE_ALL_AND_PATH, "[2]", true);
_assert("[1,2,3]", "/2", Inclusion.INCLUDE_ALL_AND_PATH, "[3]", true);

_assert("{'a':[1,2,3]}", "/a/0", Inclusion.INCLUDE_ALL_AND_PATH, "{'a':[1]}", true);
_assert("{'a':[1,2,3]}", "/a/1", Inclusion.INCLUDE_ALL_AND_PATH, "{'a':[2]}", true);
_assert("{'a':[1,2,3]}", "/a/2", Inclusion.INCLUDE_ALL_AND_PATH, "{'a':[3]}", true);

_assert("[{'id':1},{'id':2},{'id':3}]", "/0/id", Inclusion.INCLUDE_ALL_AND_PATH, "[{'id':1}]", true);
_assert("[{'id':1},{'id':2},{'id':3}]", "/1/id", Inclusion.INCLUDE_ALL_AND_PATH, "[{'id':2}]", true);
_assert("[{'id':1},{'id':2},{'id':3}]", "/2/id", Inclusion.INCLUDE_ALL_AND_PATH, "[{'id':3}]", true);

_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);
_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);
_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);
}

private void _assert(String input, String pathExpr, Inclusion tokenFilterInclusion, String exp, boolean allowMultipleMatches)
throws Exception
{
StringWriter w = new StringWriter();

JsonGenerator g0 = JSON_F.createGenerator(w);
FilteringGeneratorDelegate g = new FilteringGeneratorDelegate(g0,
new JsonPointerBasedFilter(pathExpr),
tokenFilterInclusion, false);
tokenFilterInclusion, allowMultipleMatches);

try {
writeJsonDoc(JSON_F, input, g);
Expand Down

0 comments on commit 20ef692

Please sign in to comment.