Closed cakoose closed 5 years ago
One more thing: I just tried implementing my own PrettyPrinter
from scratch. Passing in an instance of that yields the formatting I want.
However, if I change my implementation from implements PrettyPrinter
to extends DefaultPrettyPrinter
, the bug reappears. Maybe Jackson checks for instanceof DefaultPrettyPrinter
somewhere?
Updated test case:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.PrettyPrinter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.io.IOException;
public final class Test {
private static final JsonFactory jsonFactory = new JsonFactory()
.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(jsonFactory);
// changing "new DefaultPrettyPrinter" to "new PrettyPrinter" fixes the problem!
private static final PrettyPrinter PRETTY_PRINTER = new DefaultPrettyPrinter() {
private int indentationLevel = 0;
@Override
public void writeRootValueSeparator(JsonGenerator g) {}
@Override
public void writeStartObject(JsonGenerator g) throws IOException {
g.writeRaw('{');
indentationLevel++;
}
@Override
public void beforeObjectEntries(JsonGenerator g) throws IOException {
writeIndentation(g);
}
private void writeIndentation(JsonGenerator g) throws IOException {
g.writeRaw('\n');
for (int i = 0; i < indentationLevel; i++) {
g.writeRaw(" ");
}
}
@Override
public void writeObjectFieldValueSeparator(JsonGenerator g) throws IOException
{
g.writeRaw(": ");
}
@Override
public void writeObjectEntrySeparator(JsonGenerator g) throws IOException
{
g.writeRaw(',');
writeIndentation(g);
}
@Override
public void writeEndObject(JsonGenerator g, int nrOfEntries) throws IOException
{
indentationLevel--;
if (nrOfEntries > 0) {
writeIndentation(g);
}
g.writeRaw('}');
}
@Override
public void writeStartArray(JsonGenerator g) throws IOException
{
indentationLevel++;
g.writeRaw('[');
}
@Override
public void beforeArrayValues(JsonGenerator g) throws IOException {
writeIndentation(g);
}
@Override
public void writeArrayValueSeparator(JsonGenerator g) throws IOException
{
g.writeRaw(',');
writeIndentation(g);
}
@Override
public void writeEndArray(JsonGenerator g, int nrOfValues) throws IOException
{
indentationLevel--;
if (nrOfValues > 0) {
writeIndentation(g);
}
g.writeRaw(']');
}
};
public static void main(String[] args) throws IOException {
if (args.length != 0) {
System.err.println("No args allowed.");
System.exit(1); throw new AssertionError();
}
System.out.println("1. JsonGenerator directly");
{
JsonGenerator generator = jsonFactory.createGenerator(System.out).setPrettyPrinter(PRETTY_PRINTER);
generator.writeStartObject();
generator.writeFieldName("stuff");
generator.writeStartArray();
generator.writeEndArray();
generator.writeEndObject();
generator.close();
System.out.println();
}
HashMap<String, Object> data = new HashMap<>();
data.put("stuff", new String[0]);
System.out.println("2. ObjectMapper.writerWithDefaultPrettyPrinter().writeValue(out, data)");
OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValue(System.out, data);
System.out.println();
System.out.println("3. ObjectMapper.writer(PRETTY_PRINTER).writeValue(out, data)");
OBJECT_MAPPER.writer(PRETTY_PRINTER).writeValue(System.out, data);
System.out.println();
}
}
Output with new DefaultPrettyPrinter() { ... }
(bad result):
1. JsonGenerator directly
{
"stuff": []
}
2. ObjectMapper.writerWithDefaultPrettyPrinter().writeValue(out, data)
{
"stuff" : [ ]
}
3. ObjectMapper.writer(PRETTY_PRINTER).writeValue(out, data)
{
"stuff" : [ ]
}
Output with new PrettyPrinter() { ... }
(expected result):
1. JsonGenerator directly
{
"stuff": []
}
2. ObjectMapper.writerWithDefaultPrettyPrinter().writeValue(out, data)
{
"stuff" : [ ]
}
3. ObjectMapper.writer(PRETTY_PRINTER).writeValue(out, data)
{
"stuff" : [ ]
}
Actually I think I know what the problem is: you do need to override:
public DefaultPrettyPrinter createInstance() {
}
method, as anything that implements Instantiatable<?>
will get this method called.
So basically you are registering a "blueprint" instance (~= factory) -- instances are not thread-safe, so new instance needs to be created.
But I think I should change implementation in DefaultPrettyPrinter
to throw an exception in case it is sub-classed; behavior is otherwise quite unintuitive.
Ah, I see.
Maybe methods that need an instance can take PrettyPrinter
but methods that need a factory take something like Instantiable<PrettyPrinter>
?
If I was adding an API or extension point, absolutely. The reason it is this way is that it was a retrofit, having to solve statefulness problem after PrettyPrinter
was already in place.
This actually might be a good thing to do for Jackson 3.0, separating out instance, supplier. With Java 8 baseline that would be much more natural. Although could then consider question of context to pass etc... I'll file a new issue for this one.
I'm subclassing
DefaultPrettyPrinter
to override thewriteEndArray
/writeEndObject
behavior.The overrides seem to work when using
JsonGenerator
directly, but not when usingObjectMapper
.Output:
The "CALLED" lines appear when using
JsonGenerator
directly, but not when usingObjectMapper
.Additional weirdness: the "with custom PrettyPrinter" has my customized field separator, but not my customized
writeEndObject
/writeEndArray
behavior.My environment: