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;

/**
 * 
 * Thread-safe filter for JSON fields. Filters on property names, 
 *
 */

public class JacksonJsonFieldBodyFilter implements BodyFilter {

    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) {
            // ignore
        }
        return body;
}


}

@@ -414,16 +414,51 @@
 apper;%0A%0A
+import lombok.extern.slf4j.Slf4j;%0A%0A
 /**%0A * %0A
@@ -525,82 +525,355 @@
 ames
-, %0A *%0A */%0A%0Apublic class JacksonJsonFieldBodyFilter implements BodyFilter %7B
+.%0A * %3Cbr%3E%3Cbr%3E %0A * Output is always compacted, even in case of invalid JSON, %0A * so this filter should not be used in conjunction with %7B@linkplain JsonCompactor%7D.%0A *%0A */%0A%0A@Slf4j%0Apublic class JacksonJsonFieldBodyFilter implements BodyFilter %7B%0A%0A    private final static StringReplaceJsonCompactor fallbackCompactor = new StringReplaceJsonCompactor();
 %0A%0A  
@@ -2906,36 +2906,118 @@
     
-// ignore%0A        %7D%0A
+log.trace(%22Unable to filter body for fields %7B%7D, compacting result. %60%7B%7D%60%22, fields, e.getMessage()); %0A  
         
 retu
@@ -3016,23 +3016,65 @@
     
+  
 return 
+fallbackCompactor.compact(
 body
+)
 ;%0A
+        %7D%0A    
 %7D
-%0A
 %0A%0A%7D%0A

Add logging and compacting on unexpected exception during filtering