From ce845979e30dde97e90105a9869cbe301c371a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Hu=C3=9F?= Date: Tue, 11 Oct 2016 16:34:26 +0200 Subject: [PATCH] Never unquote boolean-like string content Fixes #77 --- .../dataformat/yaml/YAMLGenerator.java | 74 ++++++++++--------- .../dataformat/yaml/SimpleGenerationTest.java | 63 ++++++++++++---- 2 files changed, 89 insertions(+), 48 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java b/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java index a5c61b4..9c527cf 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java @@ -28,7 +28,7 @@ public enum Feature // implements FormatFeature // for 2.7 /** * Whether we are to write an explicit document start marker ("---") * or not. - * + * * @since 2.3 */ WRITE_DOC_START_MARKER(true), @@ -38,17 +38,17 @@ public enum Feature // implements FormatFeature // for 2.7 * or "generic" Object Id mechanism (false). Former works better for systems that * are YAML-centric; latter may be better choice for interoperability, when * converting between formats or accepting other formats. - * + * * @since 2.5 */ USE_NATIVE_OBJECT_ID(true), - + /** * Whether to use YAML native Type Id construct for indicating type (true); * or "generic" type property (false). Former works better for systems that * are YAML-centric; latter may be better choice for interoperability, when * converting between formats or accepting other formats. - * + * * @since 2.5 */ USE_NATIVE_TYPE_ID(true), @@ -80,7 +80,7 @@ public enum Feature // implements FormatFeature // for 2.7 * @since 2.7 */ MINIMIZE_QUOTES(false), - + /** * Whether numbers stored as strings will be rendered with quotes (true) or * without quotes (false, default) when MINIMIZE_QUOTES is enabled. @@ -96,7 +96,7 @@ public enum Feature // implements FormatFeature // for 2.7 protected final boolean _defaultState; protected final int _mask; - + /** * Method that calculates bit set (flags) of all features that * are enabled by default. @@ -111,14 +111,14 @@ public static int collectDefaults() } return flags; } - + private Feature(boolean defaultState) { _defaultState = defaultState; _mask = (1 << ordinal()); } - + public boolean enabledByDefault() { return _defaultState; } - public boolean enabledIn(int flags) { return (flags & _mask) != 0; } + public boolean enabledIn(int flags) { return (flags & _mask) != 0; } public int getMask() { return _mask; } } @@ -127,7 +127,7 @@ private Feature(boolean defaultState) { /* Internal constants /********************************************************** */ - + protected final static long MIN_INT_AS_LONG = (long) Integer.MIN_VALUE; protected final static long MAX_INT_AS_LONG = (long) Integer.MAX_VALUE; protected final static Pattern PLAIN_NUMBER_P = Pattern.compile("[0-9]*(\\.[0-9]*)?"); @@ -153,7 +153,7 @@ private Feature(boolean defaultState) { // for field names, leave out quotes private final static Character STYLE_NAME = null; - + // numbers, booleans, should use implicit private final static Character STYLE_SCALAR = null; // Strings quoted for fun @@ -185,7 +185,7 @@ private Feature(boolean defaultState) { * need to output one. */ protected String _typeId; - + /* /********************************************************** /* Life-cycle @@ -203,14 +203,14 @@ public YAMLGenerator(IOContext ctxt, int jsonFeatures, int yamlFeatures, _writer = out; _outputOptions = buildDumperOptions(jsonFeatures, yamlFeatures, version); - + _emitter = new Emitter(_writer, _outputOptions); // should we start output now, or try to defer? _emitter.emit(new StreamStartEvent(null, null)); Map noTags = Collections.emptyMap(); - + boolean startMarker = Feature.WRITE_DOC_START_MARKER.enabledIn(yamlFeatures); - + _emitter.emit(new DocumentStartEvent(null, null, startMarker, version, // for 1.10 was: ((version == null) ? null : version.getArray()), noTags)); @@ -232,10 +232,10 @@ protected DumperOptions buildDumperOptions(int jsonFeatures, int yamlFeatures, o return opt; } - /* - /********************************************************** - /* Versioned - /********************************************************** + /* + /********************************************************** + /* Versioned + /********************************************************** */ @Override @@ -291,7 +291,7 @@ public JsonGenerator overrideFormatFeatures(int values, int mask) { _formatFeatures = (_formatFeatures & ~mask) | (values & mask); return this; } - + @Override public boolean canUseSchema(FormatSchema schema) { return false; @@ -330,13 +330,13 @@ public YAMLGenerator configure(Feature f, boolean state) { } return this; } - + /* /********************************************************************** /* Overridden methods; writing field names /********************************************************************** */ - + /* And then methods overridden to make final, streamline some * aspects... */ @@ -389,7 +389,7 @@ public final void flush() throws IOException { _writer.flush(); } - + @Override public void close() throws IOException { @@ -406,7 +406,7 @@ public void close() throws IOException /* Public API: structural output /********************************************************** */ - + @Override public final void writeStartArray() throws IOException { @@ -422,7 +422,7 @@ public final void writeStartArray() throws IOException _emitter.emit(new SequenceStartEvent(anchor, yamlTag, implicit, null, null, style)); } - + @Override public final void writeEndArray() throws IOException { @@ -430,7 +430,7 @@ public final void writeEndArray() throws IOException _reportError("Current context not Array but "+_writeContext.typeDesc()); } // just to make sure we don't "leak" type ids - _typeId = null; + _typeId = null; _writeContext = _writeContext.getParent(); _emitter.emit(new SequenceEndEvent(null, null)); } @@ -458,7 +458,7 @@ public final void writeEndObject() throws IOException _reportError("Current context not Object but "+_writeContext.typeDesc()); } // just to make sure we don't "leak" type ids - _typeId = null; + _typeId = null; _writeContext = _writeContext.getParent(); _emitter.emit(new MappingEndEvent(null, null)); } @@ -478,7 +478,7 @@ public void writeString(String text) throws IOException,JsonGenerationException } _verifyValueWrite("write String value"); Character style = STYLE_QUOTED; - if (Feature.MINIMIZE_QUOTES.enabledIn(_formatFeatures)) { + if (Feature.MINIMIZE_QUOTES.enabledIn(_formatFeatures) && !isBooleanContent(text)) { // If this string could be interpreted as a number, it must be quoted. if (Feature.ALWAYS_QUOTE_NUMBERS_AS_STRINGS.enabledIn(_formatFeatures) && PLAIN_NUMBER_P.matcher(text).matches()) { @@ -492,6 +492,10 @@ public void writeString(String text) throws IOException,JsonGenerationException _writeScalar(text, "string", style); } + private boolean isBooleanContent(String text) { + return text.equals("true") || text.equals("false"); + } + @Override public void writeString(char[] text, int offset, int len) throws IOException { @@ -565,7 +569,7 @@ public void writeRawValue(char[] text, int offset, int len) throws IOException { /* Output method implementations, base64-encoded binary /********************************************************** */ - + @Override public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int len) throws IOException { @@ -624,13 +628,13 @@ public void writeNumber(BigInteger v) throws IOException _verifyValueWrite("write number"); _writeScalar(String.valueOf(v.toString()), "java.math.BigInteger", STYLE_SCALAR); } - + @Override public void writeNumber(double d) throws IOException { _verifyValueWrite("write number"); _writeScalar(String.valueOf(d), "double", STYLE_SCALAR); - } + } @Override public void writeNumber(float f) throws IOException @@ -681,14 +685,14 @@ public boolean canWriteObjectId() { // yes, YAML does support Native Type Ids! // 10-Sep-2014, tatu: Except as per [#23] might not want to... return Feature.USE_NATIVE_OBJECT_ID.enabledIn(_formatFeatures); - } + } @Override public boolean canWriteTypeId() { // yes, YAML does support Native Type Ids! // 10-Sep-2014, tatu: Except as per [#22] might not want to... return Feature.USE_NATIVE_TYPE_ID.enabledIn(_formatFeatures); - } + } @Override public void writeTypeId(Object id) @@ -706,7 +710,7 @@ public void writeObjectRef(Object id) AliasEvent evt = new AliasEvent(String.valueOf(id), null, null); _emitter.emit(evt); } - + @Override public void writeObjectId(Object id) throws IOException @@ -749,7 +753,7 @@ protected void _writeScalar(String value, String type, Character style) throws I { _emitter.emit(_scalarEvent(value, style)); } - + protected ScalarEvent _scalarEvent(String value, Character style) { String yamlTag = _typeId; diff --git a/src/test/java/com/fasterxml/jackson/dataformat/yaml/SimpleGenerationTest.java b/src/test/java/com/fasterxml/jackson/dataformat/yaml/SimpleGenerationTest.java index 1571fa3..3f9830d 100644 --- a/src/test/java/com/fasterxml/jackson/dataformat/yaml/SimpleGenerationTest.java +++ b/src/test/java/com/fasterxml/jackson/dataformat/yaml/SimpleGenerationTest.java @@ -40,7 +40,7 @@ public void testStreamingObject() throws Exception assertEquals("name: \"Brad\"\nage: 39", yaml); gen.close(); } - + public void testStreamingNested() throws Exception { YAMLFactory f = new YAMLFactory(); @@ -54,9 +54,9 @@ public void testStreamingNested() throws Exception gen.writeString("b"); gen.writeEndArray(); gen.writeEndObject(); - + gen.close(); - + String yaml = w.toString(); // note: 1.12 uses more compact notation; 1.10 has prefix @@ -139,7 +139,7 @@ public void testWithFile2() throws Exception assertEquals(1, result.size()); assertEquals("Foobar", result.get("name")); } - + @SuppressWarnings("resource") public void testStartMarker() throws Exception { @@ -190,7 +190,7 @@ public void testSplitLines() throws Exception assertEquals("---\n" + "- \"1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890\"", yaml); - } + } public void testLiteralStringsSingleLine() throws Exception { @@ -210,6 +210,43 @@ public void testLiteralStringsSingleLine() throws Exception "key: some value", yaml); } + public void testMinimizeQuotesWithBooleanContent() throws Exception + { + YAMLFactory f = new YAMLFactory(); + f.configure(YAMLGenerator.Feature.MINIMIZE_QUOTES, true); + + YAMLMapper mapper = new YAMLMapper(f); + + Map content = new HashMap(); + content.put("key", "true"); + String yaml = mapper.writeValueAsString(content).trim(); + + assertEquals("---\n" + + "key: \"true\"", yaml); + + content.clear(); + content.put("key", "false"); + yaml = mapper.writeValueAsString(content).trim(); + + assertEquals("---\n" + + "key: \"false\"", yaml); + + content.clear(); + content.put("key", "something else"); + yaml = mapper.writeValueAsString(content).trim(); + + assertEquals("---\n" + + "key: something else", yaml); + + content.clear(); + content.put("key", Boolean.TRUE); + yaml = mapper.writeValueAsString(content).trim(); + + assertEquals("---\n" + + "key: true", yaml); + + } + public void testLiteralStringsMultiLine() throws Exception { YAMLFactory f = new YAMLFactory(); @@ -237,7 +274,7 @@ public void testQuoteNumberStoredAsString() throws Exception f.configure(YAMLGenerator.Feature.MINIMIZE_QUOTES, true); f.configure(YAMLGenerator.Feature.ALWAYS_QUOTE_NUMBERS_AS_STRINGS, true); - + YAMLMapper mapper = new YAMLMapper(f); Map content = new HashMap(); @@ -246,14 +283,14 @@ public void testQuoteNumberStoredAsString() throws Exception assertEquals("---\n" + "key: \"20\"", yaml); - + content.clear(); content.put("key", "2.0"); yaml = mapper.writeValueAsString(content).trim(); assertEquals("---\n" + "key: \"2.0\"", yaml); - + content.clear(); content.put("key", "2.0.1.2.3"); yaml = mapper.writeValueAsString(content).trim(); @@ -270,7 +307,7 @@ public void testNonQuoteNumberStoredAsString() throws Exception assertFalse(f.isEnabled(YAMLGenerator.Feature.ALWAYS_QUOTE_NUMBERS_AS_STRINGS)); f.configure(YAMLGenerator.Feature.MINIMIZE_QUOTES, true); - + YAMLMapper mapper = new YAMLMapper(f); Map content = new HashMap(); @@ -279,14 +316,14 @@ public void testNonQuoteNumberStoredAsString() throws Exception assertEquals("---\n" + "key: 20", yaml); - + content.clear(); content.put("key", "2.0"); yaml = mapper.writeValueAsString(content).trim(); assertEquals("---\n" + "key: 2.0", yaml); - + content.clear(); content.put("key", "2.0.1.2.3"); yaml = mapper.writeValueAsString(content).trim(); @@ -300,8 +337,8 @@ public void testNonQuoteNumberStoredAsString() throws Exception /* Helper methods /********************************************************************** */ - - + + protected void _writeBradDoc(JsonGenerator gen) throws IOException { gen.writeStartObject();