Сериализуйте JsonNode в очень специфический формат JSON в Джексоне

У меня есть JsonNode результат, который я хочу распечатать. Пока что я использую:

ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
File outputFile = new File(
    getCurOutputDir(), String.format("out.json", getClass().getSimpleName())
);
mapper.writeValue(new FileOutputStream(outputFile), resultNode);

который выводит что-то вроде:

{
  "A" : [ {
    "Ai" : {
      "Ai1" : 42,
      "Ai2" : 55
    }
  } ],
    "B" : [ 86 ]
}

но мне нужно, чтобы он был в этом конкретном формате:

{
  "A" : [ 
    {
      "Ai" : {
        "Ai1" : 42,
        "Ai2" : 55
      }
    } 
  ],
    "B" : [
      86 
    ]
}

Для контекста я перехожу от JSONObject к Джексону, поэтому второй вывод — это тот, который выводится JSONObject.serialize().

Кроме того, есть ли имя для каждого из представленных выше форматов? Кажется, что он соблюдает другие стандарты.


person THIS USER NEEDS HELP    schedule 13.09.2016    source источник
comment
Я не уверен, я понимаю цель getClass().getSimpleName()) в String.format(out.json, getClass().getSimpleName()), результат все равно будет out.json   -  person Nicolas Filotto    schedule 13.09.2016


Ответы (2)


Вы можете настроить, как Джексон будет отступать на выходе. Есть разные способы добиться этого, в зависимости от версии Джексона, которую вы используете.

Джексон 2.5 и более новые версии

Сделайте следующее:

DefaultPrettyPrinter printer = new DefaultPrettyPrinter();
Indenter indenter = new DefaultIndenter();
printer.indentObjectsWith(indenter); // Indent JSON objects
printer.indentArraysWith(indenter);  // Indent JSON arrays

ObjectMapper mapper = new ObjectMapper();
mapper.writer(printer).writeValue(new FileOutputStream(outputFile), node);

По умолчанию будут использоваться 2 пробела. Для другого количества пробелов используйте DefaultIndenter конструктор, который получает строку для уровней отступа и разделитель строк:

Indenter indenter = new DefaultIndenter("      ", DefaultIndenter.SYS_LF);

Джексон 2.4 и более ранние версии

Сделайте следующее:

DefaultPrettyPrinter printer = new DefaultPrettyPrinter();
Indenter indenter = new Lf2SpacesIndenter();
printer.indentObjectsWith(indenter); // Indent JSON objects
printer.indentArraysWith(indenter);  // Indent JSON arrays

ObjectMapper mapper = new ObjectMapper();
mapper.writer(printer).writeValue(new FileOutputStream(outputFile), node);

Lf2SpacesIndenter ограничено двумя пробелами, и вы не можете изменить его.

Если вам нужно другое количество пробелов, вам нужно написать собственную реализацию. Вот тот, который использует тот же код, что и DefaultIndenter представлено в Jackson 2.5:

/**
 * Default linefeed-based indenter.
 */
public class CustomSpaceIndenter 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 CustomSpaceIndenter SYSTEM_LINEFEED_INSTANCE = 
            new CustomSpaceIndenter("  ", SYS_LF);

    /**
     * We expect to rarely get indentation deeper than this number of levels,
     * and try not to pre-generate more indentations than needed.
     */
    private final static int INDENT_LEVELS = 16;
    private final char[] indents;
    private final int charsPerLevel;
    private final String eol;

    /**
     * Indent with two spaces and the system's default line feed
     */
    public CustomSpaceIndenter() {
        this("  ", SYS_LF);
    }

    /**
     * Create an indenter which uses the <code>indent</code> string to indent one level
     *  and the <code>eol</code> string to separate lines.
     */
    public CustomSpaceIndenter(String indent, String eol)  {
        charsPerLevel = indent.length();
        indents = new char[indent.length() * INDENT_LEVELS];
        int offset = 0;
        for (int i=0; i<INDENT_LEVELS; i++) {
            indent.getChars(0, indent.length(), indents, offset);
            offset += indent.length();
        }
        this.eol = eol;
    }

    public CustomSpaceIndenter withLinefeed(String lf) {
        if (lf.equals(eol)) {
            return this;
        }
        return new CustomSpaceIndenter(getIndent(), lf);
    }

    public CustomSpaceIndenter withIndent(String indent) {
        if (indent.equals(getIndent())) {
            return this;
        }
        return new CustomSpaceIndenter(indent, eol);
    }

    public String getEol() {
        return eol;
    }

    public String getIndent() {
        return new String(indents, 0, charsPerLevel);
    }

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

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

Его можно использовать следующим образом:

Indenter indenter = new CustomSpaceIndenter("      ", CustomSpaceIndenter.SYS_LF);
person cassiomolin    schedule 13.09.2016
comment
Эй, извините за поздние изменения, но похоже, что я использую Jackson 2.4.4, а DefaultIndenter появился в 2.5. Есть ли способ обойти это без использования DefaultIndenter? - person THIS USER NEEDS HELP; 13.09.2016
comment
@THISUSERNEEDSHELP Для Jackson 2.4.4 используйте new DefaultPrettyPrinter.Lf2SpacesIndenter() вместо new DefaultIndenter(" ", DefaultIndenter.SYS_LF). Этот подход уже упоминался в ответе Jaythaking. Однако вы получите желаемый результат только в том случае, если настроите отступ для массивов (printer.indentArraysWith(indenter)). - person cassiomolin; 13.09.2016
comment
Еще раз извините за поздний запрос, но 2 пробела исправлены? Мне было интересно, можем ли мы создать отступ в 3/6/9/12/... пробелов. - person THIS USER NEEDS HELP; 15.09.2016
comment
@THISUSERNEEDSHELP Да, Lf2SpacesIndenter - это 2 фиксированных пробела. Проверьте мой обновленный ответ для подхода, который позволяет вам определить количество пробелов. - person cassiomolin; 15.09.2016

Вы можете настроить пользовательский DefaultPrettyPrinter, используя это:

DefaultPrettyPrinter pp = new DefaultPrettyPrinter();
pp.indentObjectsWith(new Lf2SpacesIndenter());
pp.indentArraysWith(new Lf2SpacesIndenter("\r\n"));
mapper.writer(pp).writeValue(new FileOutputStream(outputFile), resultNode);

Взгляните на метод, предоставленный DefaultPrettyPrinter ЗДЕСЬ

person Jaythaking    schedule 13.09.2016
comment
Кажется, это не тот же вывод, что и первый JSON? То есть [ { все еще в той же строке - person THIS USER NEEDS HELP; 13.09.2016
comment
В последних версиях Джексона это не так. Lf2SpacesIndenter устарел, начиная с Jackson 2.5, и я думаю, что он был удален в Jackson 2.7. - person cassiomolin; 13.09.2016
comment
Это не даст желаемого результата. Вы также должны делать отступы для массивов. Взгляните на мой ответ. - person cassiomolin; 13.09.2016
comment
Я дал инструмент и способ сделать. Не всегда следует ожидать готовых решений для копирования и вставки. Хорошо для вас, если вы можете улучшить мой ответ, чтобы сделать его своим... - person Jaythaking; 13.09.2016
comment
@THISUSERNEEDSHELP, чтобы сделать отступ вашего массива на другой строке, вы должны указать строку переноса в качестве атрибута для Lf2SpacesIndenter... (см. мой обновленный ответ) - person Jaythaking; 13.09.2016
comment
Здравствуйте, можно ли сделать отступ в три пробела вместо двух? Извините за позднее обновление. - person THIS USER NEEDS HELP; 15.09.2016