-
Notifications
You must be signed in to change notification settings - Fork 256
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #512 from skjolber/jsonFilter
Jackson-based body filter for JSON
- Loading branch information
Showing
5 changed files
with
208 additions
and
0 deletions.
There are no files selected for viewing
87 changes: 87 additions & 0 deletions
87
logbook-json/src/main/java/org/zalando/logbook/json/JacksonJsonFieldBodyFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
|
||
} |
92 changes: 92 additions & 0 deletions
92
logbook-json/src/test/java/org/zalando/logbook/json/JacksonJsonFieldBodyFilterTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} |