From c79a2645f7adb11085d0a59d9721df363c9a3282 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sun, 25 Sep 2022 12:22:25 +0100 Subject: [PATCH 1/3] support setting snakeyaml DumperOptions explicitly --- .../jackson/dataformat/yaml/YAMLFactory.java | 34 +++++++-- .../dataformat/yaml/YAMLFactoryBuilder.java | 71 +++++++++++++++++++ .../dataformat/yaml/YAMLGenerator.java | 24 +++++++ 3 files changed, 125 insertions(+), 4 deletions(-) diff --git a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLFactory.java b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLFactory.java index df0df35c..27fb58a0 100644 --- a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLFactory.java +++ b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLFactory.java @@ -78,6 +78,26 @@ public class YAMLFactory extends JsonFactory */ protected final LoaderOptions _loaderOptions; + /** + * Configuration for underlying generator to follow, if specified; + * left as {@code null} for backwards compatibility (which means + * the dumper options are derived based on {@link YAMLGenerator.Feature}s). + *

+ * These {@link YAMLGenerator.Feature}s are ignored if you provide your own DumperOptions: + *

+ *

+ * + * @since 2.14 + */ + protected final DumperOptions _dumperOptions; + /* /********************************************************************** /* Factory construction, configuration @@ -107,6 +127,7 @@ public YAMLFactory(ObjectCodec oc) _version = null; _quotingChecker = StringQuotingChecker.Default.instance(); _loaderOptions = null; + _dumperOptions = null; } /** @@ -120,6 +141,7 @@ public YAMLFactory(YAMLFactory src, ObjectCodec oc) _version = src._version; _quotingChecker = src._quotingChecker; _loaderOptions = src._loaderOptions; + _dumperOptions = src._dumperOptions; } /** @@ -134,6 +156,7 @@ protected YAMLFactory(YAMLFactoryBuilder b) _version = b.yamlVersionToWrite(); _quotingChecker = b.stringQuotingChecker(); _loaderOptions = b.loaderOptions(); + _dumperOptions = b.dumperOptions(); } @Override @@ -504,10 +527,13 @@ protected YAMLParser _createParser(byte[] data, int offset, int len, IOContext c @Override protected YAMLGenerator _createGenerator(Writer out, IOContext ctxt) throws IOException { int feats = _yamlGeneratorFeatures; - YAMLGenerator gen = new YAMLGenerator(ctxt, _generatorFeatures, feats, - _quotingChecker, _objectCodec, out, _version); - // any other initializations? No? - return gen; + if (_dumperOptions == null) { + return new YAMLGenerator(ctxt, _generatorFeatures, feats, + _quotingChecker, _objectCodec, out, _version); + } else { + return new YAMLGenerator(ctxt, _generatorFeatures, feats, + _quotingChecker, _objectCodec, out, _dumperOptions); + } } @Override diff --git a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLFactoryBuilder.java b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLFactoryBuilder.java index c70ee0f9..cf9f1305 100644 --- a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLFactoryBuilder.java +++ b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLFactoryBuilder.java @@ -36,6 +36,9 @@ public class YAMLFactoryBuilder extends TSFBuilder + * Ignored if you provide your own {@code DumperOptions}. + *

*/ protected DumperOptions.Version _version; @@ -53,6 +56,26 @@ public class YAMLFactoryBuilder extends TSFBuilder + * These {@link YAMLGenerator.Feature}s are ignored if you provide your own DumperOptions: + *
    + *
  • {@code YAMLGenerator.Feature.ALLOW_LONG_KEYS}
  • + *
  • {@code YAMLGenerator.Feature.CANONICAL_OUTPUT}
  • + *
  • {@code YAMLGenerator.Feature.INDENT_ARRAYS}
  • + *
  • {@code YAMLGenerator.Feature.INDENT_ARRAYS_WITH_INDICATOR}
  • + *
  • {@code YAMLGenerator.Feature.SPLIT_LINES}
  • + *
  • {@code YAMLGenerator.Feature.USE_PLATFORM_LINE_BREAKS}
  • + *
+ *

+ * + * @since 2.14 + */ + protected DumperOptions _dumperOptions; + /* /********************************************************** /* Life cycle @@ -164,6 +187,31 @@ public YAMLFactoryBuilder loaderOptions(LoaderOptions loaderOptions) { return this; } + /** + * Configuration for underlying generator to follow, if specified; + * left as {@code null} for backwards compatibility (which means + * the dumper options are derived based on {@link YAMLGenerator.Feature}s). + *

+ * These {@link YAMLGenerator.Feature}s are ignored if you provide your own DumperOptions: + *

    + *
  • {@code YAMLGenerator.Feature.ALLOW_LONG_KEYS}
  • + *
  • {@code YAMLGenerator.Feature.CANONICAL_OUTPUT}
  • + *
  • {@code YAMLGenerator.Feature.INDENT_ARRAYS}
  • + *
  • {@code YAMLGenerator.Feature.INDENT_ARRAYS_WITH_INDICATOR}
  • + *
  • {@code YAMLGenerator.Feature.SPLIT_LINES}
  • + *
  • {@code YAMLGenerator.Feature.USE_PLATFORM_LINE_BREAKS}
  • + *
+ *

+ * + * @param dumperOptions the {@code SnakeYAML} configuration to use when generating YAML + * @return This builder instance, to allow chaining + * @since 2.14 + */ + public YAMLFactoryBuilder dumperOptions(DumperOptions dumperOptions) { + _dumperOptions = dumperOptions; + return this; + } + /* /********************************************************** /* Accessors @@ -201,6 +249,29 @@ public LoaderOptions loaderOptions() { return _loaderOptions; } + /** + * Configuration for underlying generator to follow, if specified; + * left as {@code null} for backwards compatibility (which means + * the dumper options are derived based on {@link YAMLGenerator.Feature}s). + *

+ * These {@link YAMLGenerator.Feature}s are ignored if you provide your own DumperOptions: + *

    + *
  • {@code YAMLGenerator.Feature.ALLOW_LONG_KEYS}
  • + *
  • {@code YAMLGenerator.Feature.CANONICAL_OUTPUT}
  • + *
  • {@code YAMLGenerator.Feature.INDENT_ARRAYS}
  • + *
  • {@code YAMLGenerator.Feature.INDENT_ARRAYS_WITH_INDICATOR}
  • + *
  • {@code YAMLGenerator.Feature.SPLIT_LINES}
  • + *
  • {@code YAMLGenerator.Feature.USE_PLATFORM_LINE_BREAKS}
  • + *
+ *

+ * + * @return the {@code SnakeYAML} configuration to use when generating YAML + * @since 2.14 + */ + public DumperOptions dumperOptions() { + return _dumperOptions; + } + @Override public YAMLFactory build() { return new YAMLFactory(this); diff --git a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java index c96664d1..56c857fb 100644 --- a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java +++ b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java @@ -283,6 +283,30 @@ public YAMLGenerator(IOContext ctxt, int jsonFeatures, int yamlFeatures, _emitStartDocument(); } + /** + * @since 2.14 + */ + public YAMLGenerator(IOContext ctxt, int jsonFeatures, int yamlFeatures, + StringQuotingChecker quotingChecker, + ObjectCodec codec, Writer out, + org.yaml.snakeyaml.DumperOptions dumperOptions) + throws IOException + { + super(jsonFeatures, codec); + _ioContext = ctxt; + _formatFeatures = yamlFeatures; + _quotingChecker = (quotingChecker == null) + ? StringQuotingChecker.Default.instance() : quotingChecker; + _writer = out; + _docVersion = dumperOptions.getVersion(); + _outputOptions = dumperOptions; + + _emitter = new Emitter(_writer, _outputOptions); + // should we start output now, or try to defer? + _emit(new StreamStartEvent(null, null)); + _emitStartDocument(); + } + @Deprecated // since 2.12 public YAMLGenerator(IOContext ctxt, int jsonFeatures, int yamlFeatures, ObjectCodec codec, Writer out, From 5f3f4e0aa8a94f756ced0372a0c154c078724fa4 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sun, 25 Sep 2022 12:34:02 +0100 Subject: [PATCH 2/3] Update YAMLGenerator.java --- .../dataformat/yaml/YAMLGenerator.java | 66 +++++++++++++------ 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java index 56c857fb..a2be3b06 100644 --- a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java +++ b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java @@ -58,15 +58,22 @@ public enum Feature implements FormatFeature // since 2.9 /** * Do we try to force so-called canonical output or not. + *

+ * Ignored if you provide your own {@code DumperOptions}. + *

*/ CANONICAL_OUTPUT(false), /** * Options passed to SnakeYAML that determines whether longer textual content * gets automatically split into multiple lines or not. - *

- * Feature is enabled by default to conform to SnakeYAML defaults as well as - * backwards compatibility with 2.5 and earlier versions. + *

+ * Feature is enabled by default to conform to SnakeYAML defaults as well as + * backwards compatibility with 2.5 and earlier versions. + *

+ *

+ * Ignored if you provide your own {@code DumperOptions}. + *

* * @since 2.6 */ @@ -75,10 +82,11 @@ public enum Feature implements FormatFeature // since 2.9 /** * Whether strings will be rendered without quotes (true) or * with quotes (false, default). - *

- * Minimized quote usage makes for more human readable output; however, content is - * limited to printable characters according to the rules of - * literal block style. + *

+ * Minimized quote usage makes for more human readable output; however, content is + * limited to printable characters according to the rules of + * literal block style. + *

* * @since 2.7 */ @@ -87,10 +95,11 @@ public enum Feature implements FormatFeature // since 2.9 /** * Whether numbers stored as strings will be rendered with quotes (true) or * without quotes (false, default) when MINIMIZE_QUOTES is enabled. - *

- * Minimized quote usage makes for more human readable output; however, content is - * limited to printable characters according to the rules of - * literal block style. + *

+ * Minimized quote usage makes for more human readable output; however, content is + * limited to printable characters according to the rules of + * literal block style. + *

* * @since 2.8.2 */ @@ -101,8 +110,9 @@ public enum Feature implements FormatFeature // since 2.9 * literal block style * should be used. This automatically enabled when {@link #MINIMIZE_QUOTES} is set. *

- * The content of such strings is limited to printable characters according to the rules of - * literal block style. + * The content of such strings is limited to printable characters according to the rules of + * literal block style. + *

* * @since 2.9 */ @@ -111,8 +121,12 @@ public enum Feature implements FormatFeature // since 2.9 /** * Feature enabling of which adds indentation for array entry generation * (default indentation being 2 spaces). - *

- * Default value is {@code false} for backwards compatibility + *

+ * Default value is {@code false} for backwards compatibility + *

+ *

+ * Ignored if you provide your own {@code DumperOptions}. + *

* * @since 2.9 */ @@ -120,8 +134,12 @@ public enum Feature implements FormatFeature // since 2.9 /** * Feature enabling of which adds indentation with indicator for array entry generation * (default indentation being 2 spaces). - *

- * Default value is {@code false} for backwards compatibility + *

+ * Default value is {@code false} for backwards compatibility + *

+ *

+ * Ignored if you provide your own {@code DumperOptions}. + *

* * @since 2.12 */ @@ -132,7 +150,11 @@ public enum Feature implements FormatFeature // since 2.9 * serialization should be same as what the default is for current platform. * If disabled, Unix linefeed ({@code \n}) will be used. *

- * Default value is {@code false} for backwards compatibility. + * Default value is {@code false} for backwards compatibility + *

+ *

+ * Ignored if you provide your own {@code DumperOptions}. + *

* * @since 2.9.6 */ @@ -144,8 +166,12 @@ public enum Feature implements FormatFeature // since 2.9 * If disabled, the max key length is left as 128 characters: longer names * are truncated. If enabled, limit is raised to 1024 characters. *

- * Default value is {@code false} for backwards-compatibility (same as behavior - * before this feature was added). + * Default value is {@code false} for backwards-compatibility (same as behavior + * before this feature was added). + *

+ *

+ * Ignored if you provide your own {@code DumperOptions}. + *

* * @since 2.14 */ From ad2b26ee942874d065a4dba2d3ea78cef260bffa Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sun, 25 Sep 2022 12:55:41 +0100 Subject: [PATCH 3/3] Create CustomNodeStyleTest.java --- .../yaml/ser/CustomNodeStyleTest.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/ser/CustomNodeStyleTest.java diff --git a/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/ser/CustomNodeStyleTest.java b/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/ser/CustomNodeStyleTest.java new file mode 100644 index 00000000..04c892d7 --- /dev/null +++ b/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/ser/CustomNodeStyleTest.java @@ -0,0 +1,52 @@ +package com.fasterxml.jackson.dataformat.yaml.ser; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.ModuleTestBase; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; +import org.yaml.snakeyaml.DumperOptions; + +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; + +public class CustomNodeStyleTest extends ModuleTestBase { + + private final ObjectMapper REGULAR_MAPPER = createMapper(null); + private final ObjectMapper BLOCK_STYLE_MAPPER = createMapper(DumperOptions.FlowStyle.BLOCK); + private final ObjectMapper FLOW_STYLE_MAPPER = createMapper(DumperOptions.FlowStyle.FLOW); + + public void testFlowStyles() throws Exception { + // list + assertEquals("key_default:\n- value", + _asYaml(REGULAR_MAPPER, singletonMap("key_default", singletonList("value")))); + assertEquals("{key_flow: [value]}", + _asYaml(FLOW_STYLE_MAPPER, singletonMap("key_flow", singletonList("value")))); + assertEquals("key_block:\n- value", + _asYaml(BLOCK_STYLE_MAPPER, singletonMap("key_block", singletonList("value")))); + + // object + assertEquals("key_default:\n foo: bar", + _asYaml(REGULAR_MAPPER, singletonMap("key_default", singletonMap("foo", "bar")))); + assertEquals("{key_flow: {foo: bar}}", + _asYaml(FLOW_STYLE_MAPPER, singletonMap("key_flow", singletonMap("foo", "bar")))); + assertEquals("key_block:\n foo: bar", + _asYaml(BLOCK_STYLE_MAPPER, singletonMap("key_block", singletonMap("foo", "bar")))); + } + + private String _asYaml(ObjectMapper mapper, Object value) throws Exception { + return mapper.writeValueAsString(value).trim(); + } + + private ObjectMapper createMapper(DumperOptions.FlowStyle flowStyle) { + DumperOptions dumperOptions = new DumperOptions(); + if (flowStyle != null) { + dumperOptions.setDefaultFlowStyle(flowStyle); + } + YAMLFactory yamlFactory = YAMLFactory.builder().dumperOptions(dumperOptions).build(); + return YAMLMapper.builder(yamlFactory) + .enable(YAMLGenerator.Feature.MINIMIZE_QUOTES) + .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER) + .build(); + } +}