Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to configure line endings (and probably indentation) #585

Closed
digulla opened this issue Oct 16, 2014 · 10 comments
Closed

Allow to configure line endings (and probably indentation) #585

digulla opened this issue Oct 16, 2014 · 10 comments

Comments

@digulla
Copy link

digulla commented Oct 16, 2014

When writing multi-OS unit tests, it's cumbersome to filter the line endings from Jackson's output.

Please add a config option which allows to configure the line endings to use (and maybe the indentation level or String) so I can write unit tests which always produce a single Line Feed.

If I could configure the indentation, that would allow me to use tabs or four spaces to indent.

@cowtowncoder
Copy link
Member

I am guessing you are using default pretty printer (since by default Jackson does not add linefeeds).
If so, you need to implement PrettyPrinter yourself (or sub-class default one), and you can control behavior of indentation and linefeeds.

@digulla
Copy link
Author

digulla commented Oct 23, 2014

I tried that but I was unable to figure out how to make Jackson use my instance. Code from ObjectMapper.java:

protected final static PrettyPrinter _defaultPrettyPrinter = new DefaultPrettyPrinter();

final field -> no way to change it.

Also, I couldn't figure out how to make ObjectMapper pass my preferred PrettyPrinter to JsonGenerator instances it creates. Instead, it calls useDefaultPrettyPrinter() which contains new DefaultPrettyPrinter(), so I can't override this as well.

Lastly, I would have to copy the whole class Lf2SpacesIndenter since it's hostile to any kind of reuse.

To add insult to injury, my Eclipse refuses to open GeneratorBase :-(

Would you accept a pull request?

@cowtowncoder
Copy link
Member

Ah, no, don't try to change definition of default pretty-printing; rather, assign your own PrettyPrinter, either for mapper, or, via ObjectWriter. Once assigned it will be properly assigned to JsonGenerator that is constructored. Or, if you construct generator yourself, you need to directly assign it (generator.setPrettyPrinter() or such).

You should not have to change anything in JsonGenerator, ObjectMapper or ObjectWriter.
I don't remember if I intended Lf2SpacesIndenter to be easily sub-classable or not, but I am open to changes there.

@digulla
Copy link
Author

digulla commented Oct 24, 2014

Suggestion for the indenter:

/**
 * Default linefeed-based indenter uses system-specific linefeeds and
 * 2 spaces for indentation per level.
 */
public class DefaultIndenter
    extends DefaultPrettyPrinter.NopIndenter
{
    public final static String SYS_LF;
    static {
        String lf;
        try {
            lf = System.getProperty("line.separator");
        } catch (Throwable t) {
            lf = "\n"; // fallback when security manager denies access
        }
        SYS_LF = lf;
    }

    public static final DefaultIndenter instance = new DefaultIndenter("  ", SYS_LF);

    private final static int INDENT_LEVELS = 64;
    private final char[] indents;
    private final int charsPerLevel;
    private final String eol;

    /** Create an indenter which uses the <code>indent</code> string to indent one level
     *  and the <code>eol</code> string to separate lines. */
    public DefaultIndenter(String indent, String eol) {
        charsPerLevel = indent.length();

        this.indents = new char[indent.length() * INDENT_LEVELS];
        int offset = 0;
        for (int i=0; i<INDENT_LEVELS; i++) {
            indent.getChars(0, indent.length(), this.indents, offset);
            offset += indent.length();
        }

        this.eol = eol;
    }

    @Override
    public boolean isInline() { return false; }

    @Override
    public void writeIndentation(JsonGenerator jg, int level)
        throws IOException, JsonGenerationException
    {
        jg.writeRaw(eol);
        if (level > 0) { // should we err on negative values (as there's some flaw?)
            level *= charsPerLevel;
            while (level > indents.length) { // should never happen but...
                jg.writeRaw(indents, 0, indents.length); 
                level -= indents.length;
            }
            jg.writeRaw(indents, 0, level);
        }
    }
}

@digulla
Copy link
Author

digulla commented Oct 24, 2014

Can you please provide an example how to configure ObjectMapper in such a way that my PrettyPrinter is used everywhere? I can't figure this out from the documentation or the code.

@cowtowncoder
Copy link
Member

You will need to use ObjectWriter actually:

ObjectWriter w = mapper.writer(new MyPrettyPrinter());
w.writeValue(output, value);

and either hold on to writer, or just create instances dynamically; they are cheap to create unlike ObjectMapper instances.

Use of ObjectWriter is recommended in general since they are stateless, immutable, light-weight and easily (re)configurable.

@digulla
Copy link
Author

digulla commented Nov 13, 2014

Sent you a pull request for the new DefaultIndenter above: FasterXML/jackson-core#166

@derknorton
Copy link

Interesting discussion... I know this issue is already closed but it still sounds like a bug to me. I use the ObjectMapper for serialization and deserialization of my POJOs into JSON as part of my jackson based RESTful web services. I don't know a way to do this with the ObjectWriter, perhaps its possible.

So I would like to add my own custom PrettyPrinter subclass that does not use the inline formatting for arrays/collections while serializing the JSON for my web service. I need to do what @digulla was requesting above and have the ObjectMapper use my custom PrettyPrinter everywhere. It seems like this should be something that is possible and if not, perhaps should be considered a bug.

@cowtowncoder
Copy link
Member

@derknorton what sounds like a bug? Pull request #166 was merged, included in 2.5.0.

As to semantics, while it's not a huge deal, missing functionality is not a bug, but, well, missing functionality. Creating a new issue for adding things that are missing and you would like to see is perfectly fine; and in fact I prefer it over re-opening old issues (unless it is specific flaw in fix/addition itself).

ghost pushed a commit to facebook/buck that referenced this issue Feb 12, 2016
Summary:This upgrade was motivated by the need to pick up the fix for
FasterXML/jackson-databind#585.

Basically, we want to make sure that `BUCK.autodeps` files are byte-for-byte
identical, regardless of whether it is run on Windows or non-Windows.
As such, we need to be able to specfiy the line endings used by the Jackson
pretty-printer.

After downloading the appropriate JARs from Maven, and replacing them in third-party,
I ran the following commands to help create this change:

```
git grep -l jackson-annotations-2.0.5 | xargs sed -i '' -e 's#jackson-annotations-2.0.5#jackson-annotations-2.5.5#g'
git grep -l jackson-core-2.0.5 | xargs sed -i '' -e 's#jackson-core-2.0.5#jackson-core-2.5.5#g'
git grep -l jackson-databind-2.0.5 | xargs sed -i '' -e 's#jackson-databind-2.0.5#jackson-databind-2.5.5#g'
```

After that, I had to fix up a bunch of Jackson API calls because some of them had become
deprecated in favor of new methods. The existing JSON pretty printer that we were using
in `AutodepsWriter` was one such deprecation, so I fixed the line-ending issue while there.

Test Plan:* Ran `buck autodeps` and verified that no `BUCK.autodeps` files changed.
* CI

Reviewed By: sdwilsh

fb-gh-sync-id: eec12c6
shipit-source-id: eec12c6
@james-bjss
Copy link

james-bjss commented Jun 12, 2020

Realise this ticket is closed, but we faced a similar issue when matching messages in unit tests on Windows machines. So adding this here for others who stumble upon this page.

The DefaultPrettyPrinter was adding \r\n but we were expecting \n.

The line-feed char can be set as follows:

private static final ObjectWriter objectWriter = new ObjectMapper().writer(new DefaultPrettyPrinter()
            .withObjectIndenter(new DefaultIndenter().withLinefeed("\n")));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants