-
-
Notifications
You must be signed in to change notification settings - Fork 151
Add FlowStyleResolver to enable custom YAML node style resolution #242
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,8 @@ | |
import java.util.Map; | ||
import java.util.regex.Pattern; | ||
|
||
import com.fasterxml.jackson.dataformat.yaml.util.NodeStyleResolver; | ||
import com.fasterxml.jackson.dataformat.yaml.util.NodeStyleResolver.NodeStyle; | ||
import org.yaml.snakeyaml.DumperOptions; | ||
import org.yaml.snakeyaml.DumperOptions.FlowStyle; | ||
import org.yaml.snakeyaml.emitter.Emitter; | ||
|
@@ -241,6 +243,8 @@ private Feature(boolean defaultState) { | |
|
||
protected final StringQuotingChecker _quotingChecker; | ||
|
||
protected final NodeStyleResolver _nodeStyleResolver; | ||
|
||
/* | ||
/********************************************************** | ||
/* Life-cycle | ||
|
@@ -249,6 +253,7 @@ private Feature(boolean defaultState) { | |
|
||
public YAMLGenerator(IOContext ctxt, int jsonFeatures, int yamlFeatures, | ||
StringQuotingChecker quotingChecker, | ||
NodeStyleResolver nodeStyleResolver, | ||
ObjectCodec codec, Writer out, | ||
org.yaml.snakeyaml.DumperOptions.Version version) | ||
throws IOException | ||
|
@@ -258,6 +263,8 @@ public YAMLGenerator(IOContext ctxt, int jsonFeatures, int yamlFeatures, | |
_formatFeatures = yamlFeatures; | ||
_quotingChecker = (quotingChecker == null) | ||
? StringQuotingChecker.Default.instance() : quotingChecker; | ||
_nodeStyleResolver = (nodeStyleResolver == null) | ||
? NodeStyleResolver.DEFAULT_INSTANCE : nodeStyleResolver; | ||
_writer = out; | ||
_docVersion = version; | ||
|
||
|
@@ -273,7 +280,7 @@ public YAMLGenerator(IOContext ctxt, int jsonFeatures, int yamlFeatures, | |
public YAMLGenerator(IOContext ctxt, int jsonFeatures, int yamlFeatures, | ||
ObjectCodec codec, Writer out, | ||
org.yaml.snakeyaml.DumperOptions.Version version) throws IOException { | ||
this(ctxt, jsonFeatures, yamlFeatures, null, | ||
this(ctxt, jsonFeatures, yamlFeatures, null, null, | ||
codec, out, version); | ||
} | ||
|
||
|
@@ -517,13 +524,16 @@ public final void writeStartArray() throws IOException | |
{ | ||
_verifyValueWrite("start an array"); | ||
_writeContext = _writeContext.createChildArrayContext(); | ||
FlowStyle style = _outputOptions.getDefaultFlowStyle(); | ||
String yamlTag = _typeId; | ||
boolean implicit = (yamlTag == null); | ||
String anchor = _objectId; | ||
if (anchor != null) { | ||
_objectId = null; | ||
} | ||
NodeStyle jacksonStyle = _nodeStyleResolver.resolveStyle( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would make sense to use Would this work with nested arrays? (might be that it gets Also: would make sense to get and pass default style to custom resolver -- but resolver could be passed the context as well (pre-addition of array), to let it access all information, including ancestors. I'll add a note on interface later on. |
||
_writeContext.getParent().getCurrentName()); | ||
FlowStyle style = jacksonStyle != null ? jacksonStyle.getSnakeYamlFlowStyle() | ||
: _outputOptions.getDefaultFlowStyle(); | ||
_emit(new SequenceStartEvent(anchor, yamlTag, | ||
implicit, null, null, style)); | ||
} | ||
|
@@ -545,13 +555,16 @@ public final void writeStartObject() throws IOException | |
{ | ||
_verifyValueWrite("start an object"); | ||
_writeContext = _writeContext.createChildObjectContext(); | ||
FlowStyle style = _outputOptions.getDefaultFlowStyle(); | ||
String yamlTag = _typeId; | ||
boolean implicit = (yamlTag == null); | ||
String anchor = _objectId; | ||
if (anchor != null) { | ||
_objectId = null; | ||
} | ||
NodeStyle jacksonStyle = _nodeStyleResolver.resolveStyle( | ||
_writeContext.getParent().getCurrentName()); | ||
FlowStyle style = jacksonStyle != null ? jacksonStyle.getSnakeYamlFlowStyle() | ||
: _outputOptions.getDefaultFlowStyle(); | ||
_emit(new MappingStartEvent(anchor, yamlTag, | ||
implicit, null, null, style)); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package com.fasterxml.jackson.dataformat.yaml.util; | ||
|
||
import org.yaml.snakeyaml.DumperOptions; | ||
|
||
/** | ||
* Helper interface to customize node styles of object and arrays while exporting YAML objects. | ||
* | ||
* @see NodeStyle | ||
* @see #resolveStyle(String) | ||
*/ | ||
public interface NodeStyleResolver { | ||
|
||
/** | ||
* Defines which style to apply to a given object or array. | ||
* | ||
* @see DumperOptions.FlowStyle | ||
* @see <a href="http://www.yaml.org/spec/current.html#id2509255">3.2.3.1. | ||
* Node Styles (http://yaml.org/spec/1.1)</a> | ||
*/ | ||
enum NodeStyle { | ||
/** | ||
* Block style. i.e. | ||
* <pre> | ||
* foo: | ||
* - bar | ||
* </pre> | ||
* <pre> | ||
* key: | ||
* foo: bar | ||
* </pre> | ||
*/ | ||
BLOCK, | ||
/** | ||
* Flow style. i.e. | ||
* <pre> | ||
* foo: [bar] | ||
* </pre> | ||
* <pre> | ||
* key: {foo: bar} | ||
* </pre> | ||
*/ | ||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I realized that instead of passing just At very least I think array and object cases should go to different methods since I think that's relatively common use case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ... just realized I added this note already. Wrt comment I just added above. And looking at this method now, maybe passing the default style is not useful after all (because it would need to be translated etc). Returning |
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.