Skip to content

Commit

Permalink
Merge pull request #512 from skjolber/jsonFilter
Browse files Browse the repository at this point in the history
Jackson-based body filter for JSON
  • Loading branch information
Willi Schönborn committed Jun 13, 2019
2 parents ba3982d + cfedf2f commit 3782861
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package org.zalando.logbook.json;

import java.io.StringWriter;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.zalando.logbook.BodyFilter;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.extern.slf4j.Slf4j;

/**
*
* Thread-safe filter for JSON fields. Filters on property names.
* <br><br>
* Output is always compacted, even in case of invalid JSON,
* so this filter should not be used in conjunction with {@linkplain JsonCompactor}.
*
*/

@Slf4j
public class JacksonJsonFieldBodyFilter implements BodyFilter {

private final static StringReplaceJsonCompactor fallbackCompactor = new StringReplaceJsonCompactor();

private final String replacement;
private final Set<String> fields;
private final ObjectMapper objectMapper;

public JacksonJsonFieldBodyFilter(Collection<String> fieldNames, String replacement, ObjectMapper objectMapper) {
this.fields = new HashSet<>(fieldNames); // thread safe for reading
this.replacement = replacement;
this.objectMapper = objectMapper;
}

public JacksonJsonFieldBodyFilter(Collection<String> fieldNames, String replacement) {
this(fieldNames, replacement, new ObjectMapper());
}

@Override
public String filter(String contentType, String body) {
return JsonMediaType.JSON.test(contentType) ? filter(body) : body;
}

public String filter(final String body) {
try {
JsonFactory factory = objectMapper.getFactory();
final JsonParser parser = factory.createParser(body);

StringWriter writer = new StringWriter(body.length() * 2); // rough estimate of final size

JsonGenerator generator = factory.createGenerator(writer);
try {
while(true) {
JsonToken nextToken = parser.nextToken();
if(nextToken == null) {
break;
}

generator.copyCurrentEvent(parser);
if(nextToken == JsonToken.FIELD_NAME && fields.contains(parser.getCurrentName())) {
nextToken = parser.nextToken();
generator.writeString(replacement);
if(!nextToken.isScalarValue()) {
parser.skipChildren(); // skip children
}
}
}
} finally {
parser.close();

generator.close();
}

return writer.toString();
} catch(Exception e) {
log.trace("Unable to filter body for fields {}, compacting result. `{}`", fields, e.getMessage());
return fallbackCompactor.compact(body);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package org.zalando.logbook.json;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;

import org.junit.jupiter.api.Test;


public class JacksonJsonFieldBodyFilterTest {

@Test
public void testFilterString() throws Exception {
String filtered = getFilter("email") .filter(getResource("/user.json"));
assertThat(filtered, not(containsString("@entur.org")));
}

@Test
public void testFilterNumber() throws Exception {
String filtered = getFilter("id") .filter(getResource("/user.json"));
assertThat(filtered, not(containsString("18375")));
}

@Test
public void testFilterObject() throws Exception {
String filtered = getFilter("cars") .filter(getResource("/cars-object.json"));
assertThat(filtered, not(containsString("Ford")));
}

@Test
public void testFilterArray() throws Exception {
String filtered = getFilter("cars") .filter(getResource("/cars-array.json"));
assertThat(filtered, not(containsString("Ford")));
}

@Test
public void testFilterHugeBodyObject1() throws Exception {
String string = getResource("/huge-sample.json");
String filtered = getFilter("name").filter(string);
assertThat(filtered, not(containsString("Pena Hudson")));
assertThat(filtered.length(), is(lessThan(string.length())));
}

@Test
public void testFilterHugeBodyObject2() throws Exception {
Set<String> remove = new HashSet<>();
remove.add("name");
String string = getResource("/huge-sample.json");
String filtered = getFilter(remove).filter("application/json", string);
assertThat(filtered, not(containsString("Pena Hudson")));
assertThat(filtered.length(), is(lessThan(string.length())));
}

@Test
public void doesNotFilterInvalidJson() throws Exception {
String valid = getResource("/cars-array.json").trim();
String invalid = valid.substring(0, valid.length() - 1);
String filtered = getFilter("cars").filter(invalid);
assertThat(filtered, containsString("Ford"));
}

@Test
public void doesNotFilterNonJson() throws Exception {
String valid = getResource("/cars-array.json").trim();
String invalid = valid.substring(0, valid.length() - 1);
String filtered = getFilter("cars").filter("application/xml", invalid);
assertThat(filtered, containsString("Ford"));
}

private String getResource(String path) throws IOException {
final byte[] bytes = Files.readAllBytes(Paths.get("src/test/resources/" + path));
return new String(bytes, UTF_8);
}

public static JacksonJsonFieldBodyFilter getFilter(String ... fieldNames) {
return new JacksonJsonFieldBodyFilter(Arrays.asList(fieldNames), "XXX");
}

public static JacksonJsonFieldBodyFilter getFilter(Collection<String> fieldNames) {
return new JacksonJsonFieldBodyFilter(fieldNames , "XXX");
}

}
9 changes: 9 additions & 0 deletions logbook-json/src/test/resources/cars-array.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name":"John",
"age":30,
"cars": [
{"car1":"Ford"},
{"car2":"BMW"},
{"car3":"Fiat"}
]
}
9 changes: 9 additions & 0 deletions logbook-json/src/test/resources/cars-object.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name":"John",
"age":30,
"cars": {
"car1":"Ford",
"car2":"BMW",
"car3":"Fiat"
}
}
11 changes: 11 additions & 0 deletions logbook-json/src/test/resources/user.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": 1,
"customerNumber": 1234567,
"profileType": "S",
"status": "A",
"firstName": "My",
"surname": "Name",
"email": "someone@zalando.org",
"languagePreference": "NO",
"uuid": "658DE1D4BB4F3AC4E053020011AC1EC3"
}

0 comments on commit 3782861

Please sign in to comment.