Jackson YAML 序列化对象数组格式

Jackson YAML Serialization Object Arrays Format

我正在尝试以某种方式格式化我的 Jackson Yaml 序列化。

employees:
 - name: John
   age: 26
 - name: Bill
   age: 17

但是,当我序列化对象时,这是我得到的格式。

employees:
 -
  name: John
  age: 26
 -
  name: Bill
  age: 17

有什么方法可以去掉数组中对象开头的换行符吗?这纯粹是个人 preference/human 可读性问题。

这些是我当前在 YAMLFactory 上设置的属性:

YAMLFactory yamlFactory = new YAMLFactory()
                .enable(YAMLGenerator.Feature.MINIMIZE_QUOTES) //removes quotes from strings
                .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER)//gets rid of -- at the start of the file.
                .enable(YAMLGenerator.Feature.INDENT_ARRAYS);// enables indentation.

我查看了 Jackson 中 YAMLGenerator 的 java 文档,并查看了关于 Whosebug 的其他问题,但我找不到一个选项来做我想做的事情。

我已经尝试了 CANONICAL_OUTPUT、SPLIT_LINES 和 LITERAL_BLOCK_STYLE 属性,最后一个在设置 MINIMIZE_QUOTES 时自动设置。 CANONICAL_OUTPUT 似乎在数组周围添加了括号。 SPLIT_LINES 和 LITERAL_BLOCK_STYLE 与多行字符串的处理方式有关。

简短的回答是目前没有办法通过 Jackson 做到这一点。这是由于 snakeyaml 中的一个错误,如果您设置 indicatorIndent 属性,则空格未正确处理,因此 snakeyaml 添加了新行。

我找到了直接使用 snakeyaml 的解决方法。

//The representer allows us to ignore null properties, and to leave off the class definitions
Representer representer = new Representer() {
    //ignore null properties
    @Override
    protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) {
        // if value of property is null, ignore it.
        if (propertyValue == null) {
            return null;
        }
        else {
            return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag);
        }
    }

    //Don't print the class definition
    @Override
    protected MappingNode representJavaBean(Set<Property> properties, Object javaBean) {
        if (!classTags.containsKey(javaBean.getClass())){
            addClassTag(javaBean.getClass(), Tag.MAP);
           }

        return super.representJavaBean(properties, javaBean);
    }
};



DumperOptions dumperOptions = new DumperOptions();
//prints the yaml as nested blocks
dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
//indicatorIndent indents the '-' character for lists
dumperOptions.setIndicatorIndent(2);
//This is the workaround. Indent must be set to at least 2 higher than the indicator indent because of how whitespace is handled.
//If not set to 2 higher, then the newline is added.
dumperOptions.setIndent(4);
Yaml yaml = new Yaml(representer, dumperOptions);
//prints the object to a yaml string.
yaml.dump(object);

解决方法是在 DumperOptions 上设置缩进 属性。您需要将缩进设置为至少比 indicatorIndent 高 2 的值,否则将添加换行符。这是由于在 snakeyaml 中处理空格的方式。

查看 Jackson 源代码,snakeyaml 转储程序选项的创建方式如下: @Andrey 你觉得这好看吗?

    protected DumperOptions buildDumperOptions(int jsonFeatures, int yamlFeatures,
            org.yaml.snakeyaml.DumperOptions.Version version)
    {
        DumperOptions opt = new DumperOptions();
        // would we want canonical?
        if (Feature.CANONICAL_OUTPUT.enabledIn(_formatFeatures)) {
            opt.setCanonical(true);
        } else {
            opt.setCanonical(false);
            // if not, MUST specify flow styles
            opt.setDefaultFlowStyle(FlowStyle.BLOCK);
        }
        // split-lines for text blocks?
        opt.setSplitLines(Feature.SPLIT_LINES.enabledIn(_formatFeatures));
        // array indentation?
        if (Feature.INDENT_ARRAYS.enabledIn(_formatFeatures)) {
            // But, wrt [dataformats-text#34]: need to set both to diff values to work around bug
            // (otherwise indentation level is "invisible". Note that this should NOT be necessary
            // but is needed up to at least SnakeYAML 1.18.
            // Also looks like all kinds of values do work, except for both being 2... weird.
            opt.setIndicatorIndent(1);
            opt.setIndent(2);
        }
        // 14-May-2018: [dataformats-text#84] allow use of platform linefeed
        if (Feature.USE_PLATFORM_LINE_BREAKS.enabledIn(_formatFeatures)) {
            opt.setLineBreak(DumperOptions.LineBreak.getPlatformLineBreak());
        }
        return opt;
    }

我 运行 对此感兴趣并最终写了这篇博客文章来描述我想出的解决方案。简而言之,我创建了 YAMLGeneratorYAMLFactory 的自定义子类,并用它来配置 Jackson 的 YAMLMapper。不“干净”,但不是很大且相当有效。让我任意设置DumperOptions

以下来源,但 also available in this gist

警告 - 我在 Kotlin 中完成了这一切,但这是微不足道的代码,应该很容易 back-portable 到 Java:

val mapper: YAMLMapper = YAMLMapper(MyYAMLFactory()).apply {
  registerModule(KotlinModule())
  setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
}


class MyYAMLGenerator(
  ctx: IOContext,
  jsonFeatures: Int,
  yamlFeatures: Int,
  codec: ObjectCodec,
  out: Writer,
  version: DumperOptions.Version?
): YAMLGenerator(ctx, jsonFeatures, yamlFeatures, codec, out, version) {
  override fun buildDumperOptions(
    jsonFeatures: Int,
    yamlFeatures: Int,
    version: DumperOptions.Version?
  ): DumperOptions {
    return super.buildDumperOptions(jsonFeatures, yamlFeatures, version).apply {
      //
      // NOTE: CONFIGURATION HAPPENS HERE!!
      //
      defaultScalarStyle = ScalarStyle.LITERAL;
      defaultFlowStyle = FlowStyle.BLOCK
      indicatorIndent = 2
      nonPrintableStyle = ESCAPE
      indent = 4
      isPrettyFlow = true
      width = 100
      this.version = version
    }
  }
}

class MyYAMLFactory(): YAMLFactory() {
  @Throws(IOException::class)
  override fun _createGenerator(out: Writer, ctxt: IOContext): YAMLGenerator {
    val feats = _yamlGeneratorFeatures
    return MyYAMLGenerator(ctxt, _generatorFeatures, feats,_objectCodec, out, _version)
  }
}

由于 YAMLGenerator.Feature.INDENT_ARRAYS_WITH_INDICATOR 的引入,在今天至少使用 jackson-dataformat-yaml 版本 2.12.x 的环境中,您可以简单地执行以下操作:

public ObjectMapper yamlObjectMapper() {
    final YAMLFactory factory = new YAMLFactory()
            .enable(YAMLGenerator.Feature.INDENT_ARRAYS_WITH_INDICATOR)
            .enable(YAMLGenerator.Feature.MINIMIZE_QUOTES)
            .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);

    return new ObjectMapper(factory);
}

这将为您提供以下输出:

employees:
 - name: John
   age: 26
 - name: Bill
   age: 17