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 f391ca5c..92de6a7e 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 @@ -4,6 +4,7 @@ import java.net.URL; import java.nio.charset.Charset; +import com.fasterxml.jackson.dataformat.yaml.util.NodeStyleResolver; import org.yaml.snakeyaml.DumperOptions; import com.fasterxml.jackson.core.*; @@ -66,6 +67,13 @@ public class YAMLFactory extends JsonFactory */ protected final StringQuotingChecker _quotingChecker; + /** + * Helper object used to determine node styles of objects and arrays. + * + * @since 2.13 + */ + protected final NodeStyleResolver _nodeStyleResolver; + /* /********************************************************************** /* Factory construction, configuration @@ -94,6 +102,7 @@ public YAMLFactory(ObjectCodec oc) //_version = DumperOptions.Version.V1_1; _version = null; _quotingChecker = StringQuotingChecker.Default.instance(); + _nodeStyleResolver = NodeStyleResolver.DEFAULT_INSTANCE; } /** @@ -106,6 +115,7 @@ public YAMLFactory(YAMLFactory src, ObjectCodec oc) _yamlGeneratorFeatures = src._yamlGeneratorFeatures; _version = src._version; _quotingChecker = src._quotingChecker; + _nodeStyleResolver = src._nodeStyleResolver; } /** @@ -119,6 +129,7 @@ protected YAMLFactory(YAMLFactoryBuilder b) _yamlGeneratorFeatures = b.formatGeneratorFeaturesMask(); _version = b.yamlVersionToWrite(); _quotingChecker = b.stringQuotingChecker(); + _nodeStyleResolver = b.nodeStyleResolver(); } @Override @@ -490,7 +501,7 @@ protected YAMLParser _createParser(byte[] data, int offset, int len, IOContext c protected YAMLGenerator _createGenerator(Writer out, IOContext ctxt) throws IOException { int feats = _yamlGeneratorFeatures; YAMLGenerator gen = new YAMLGenerator(ctxt, _generatorFeatures, feats, - _quotingChecker, _objectCodec, out, _version); + _quotingChecker, _nodeStyleResolver, _objectCodec, out, _version); // any other initializations? No? return gen; } 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 235aa641..e2d55f13 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 @@ -1,5 +1,6 @@ package com.fasterxml.jackson.dataformat.yaml; +import com.fasterxml.jackson.dataformat.yaml.util.NodeStyleResolver; import org.yaml.snakeyaml.DumperOptions; import com.fasterxml.jackson.core.TSFBuilder; @@ -33,6 +34,13 @@ public class YAMLFactoryBuilder extends TSFBuilder3.2.3.1. + * Node Styles (http://yaml.org/spec/1.1) + */ + enum NodeStyle { + /** + * Block style. i.e. + *
+         *   foo:
+         *   - bar
+         * 
+ *
+         *   key:
+         *     foo: bar
+         * 
+ */ + BLOCK, + /** + * Flow style. i.e. + *
+         *   foo: [bar]
+         * 
+ *
+         *   key: {foo: bar}
+         * 
+ */ + FLOW; + + public DumperOptions.FlowStyle getSnakeYamlFlowStyle() { + switch (this) { + case BLOCK: + return DumperOptions.FlowStyle.BLOCK; + case FLOW: + return DumperOptions.FlowStyle.FLOW; + default: + throw new IllegalStateException("Unexpected value: " + this); + } + } + } + + NodeStyleResolver DEFAULT_INSTANCE = new NodeStyleResolver() { + @Override + public NodeStyle resolveStyle(String fieldName) { + // default behaviour uses YAMLGenerator._outputOptions.getDefaultFlowStyle() (currently set to BLOCK) + return null; + } + }; + + /** + * Resolve a node style for given fieldName. + * + * @param fieldName parent field name of the current object or array. can be null if there is no parent field (i.e. + * typically root object) + * @return the desired {@link NodeStyle} or null to use default value (currently 'BLOCK') + */ + NodeStyle resolveStyle(String fieldName); + +} 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..05d57481 --- /dev/null +++ b/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/ser/CustomNodeStyleTest.java @@ -0,0 +1,60 @@ +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 com.fasterxml.jackson.dataformat.yaml.util.NodeStyleResolver; + +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; + +public class CustomNodeStyleTest extends ModuleTestBase { + static class CustomNodeStyleResolver implements NodeStyleResolver { + @Override + public NodeStyle resolveStyle(String fieldName) { + if (fieldName != null && fieldName.endsWith("_flow")) + return NodeStyle.FLOW; + else if (fieldName != null && fieldName.endsWith("_block")) + return NodeStyle.BLOCK; + else + return null; + } + } + + private final ObjectMapper REGULAR_MAPPER = YAMLMapper.builder() + .enable(YAMLGenerator.Feature.MINIMIZE_QUOTES) + .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER) + .build(); + + private final YAMLMapper CUSTOM_MAPPER = YAMLMapper.builder( + YAMLFactory.builder() + .nodeStyleResolver(new CustomNodeStyleResolver()) + .build()) + .enable(YAMLGenerator.Feature.MINIMIZE_QUOTES) + .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER) + .build(); + + public void testFlowStyles() throws Exception { + // list + assertEquals("key_flow: [value]", + _asYaml(CUSTOM_MAPPER, singletonMap("key_flow", singletonList("value")))); + assertEquals("key_block:\n- value", + _asYaml(CUSTOM_MAPPER, singletonMap("key_block", singletonList("value")))); + assertEquals("key_default:\n- value", + _asYaml(REGULAR_MAPPER, singletonMap("key_default", singletonList("value")))); + + // object + assertEquals("key_flow: {foo: bar}", + _asYaml(CUSTOM_MAPPER, singletonMap("key_flow", singletonMap("foo", "bar")))); + assertEquals("key_block:\n foo: bar", + _asYaml(CUSTOM_MAPPER, singletonMap("key_block", singletonMap("foo", "bar")))); + assertEquals("key_default:\n foo: bar", + _asYaml(REGULAR_MAPPER, singletonMap("key_default", singletonMap("foo", "bar")))); + } + + private String _asYaml(ObjectMapper mapper, Object value) throws Exception { + return mapper.writeValueAsString(value).trim(); + } +}