Skip to content

Commit c427465

Browse files
committed
Fixed #116
1 parent 10f2dc1 commit c427465

File tree

4 files changed

+97
-42
lines changed

4 files changed

+97
-42
lines changed

release-notes/CREDITS-2.x

+4
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,7 @@ Andrey Somov (asomov@github)
5959
Alon Bar-Lev (alonbl@github)
6060
* Contributed #100: (properties) Add an option to specify properties prefix
6161
(2.10.0)
62+
63+
Stehan Leh (stefanleh@github)
64+
* Reported #116: (yaml) Error handling "null" String when Feature.MINIMIZE_QUOTES is active
65+
(2.10.0)

release-notes/VERSION-2.x

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ Modules:
1818
(contributed by Andrey S)
1919
#108: (yaml) Add new `CsvParser.Feature.ALLOW_COMMENTS` to replace deprecated
2020
`JsonParser.Feature.ALLOW_YAML_COMMENTS`
21+
#116: Error handling "null" String when Feature.MINIMIZE_QUOTES is active
22+
(reported by Stefan L)
2123
- Add JDK9+ module info using Moditect plugin
2224

2325
2.9.9 (16-May-2019)

yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java

+63-42
Original file line numberDiff line numberDiff line change
@@ -177,13 +177,23 @@ private Feature(boolean defaultState) {
177177
*/
178178
// 02-Apr-2019, tatu: Some names will look funny if escaped: let's leave out
179179
// single letter case (esp so 'y' won't get escaped)
180-
private final static Set<String> RESERVED_NAMES = new HashSet<>(Arrays.asList(
180+
private final static Set<String> MUST_QUOTE_NAMES = new HashSet<>(Arrays.asList(
181181
// "y", "Y", "n", "N",
182182
"yes", "Yes", "YES", "no", "No", "NO",
183183
"true", "True", "TRUE", "false", "False", "FALSE",
184184
"on", "On", "ON", "off", "Off", "OFF"
185185
));
186186

187+
/**
188+
* As per YAML <a href="https://yaml.org/type/null.html">null</a>
189+
* and <a href="https://yaml.org/type/bool.html">boolean</a> type specs,
190+
* better retain quoting for some values
191+
*/
192+
private final static Set<String> MUST_QUOTE_VALUES = new HashSet<>(Arrays.asList(
193+
"true", "True", "TRUE", "false", "False", "FALSE",
194+
"null", "Null", "NULL"
195+
));
196+
187197
/*
188198
/**********************************************************
189199
/* Configuration
@@ -444,35 +454,9 @@ public final void writeStringField(String fieldName, String value)
444454
private final void _writeFieldName(String name) throws IOException
445455
{
446456
_writeScalar(name, "string",
447-
_needQuoting(name) ? STYLE_QUOTED : STYLE_UNQUOTED_NAME);
457+
_nameNeedsQuoting(name) ? STYLE_QUOTED : STYLE_UNQUOTED_NAME);
448458
}
449459

450-
private boolean _needQuoting(String name) {
451-
if (name.length() == 0) {
452-
return false;
453-
}
454-
switch (name.charAt(0)) {
455-
// First, reserved name starting chars:
456-
case 'f': // false
457-
case 'o': // on/off
458-
case 'n': // no
459-
case 't': // true
460-
case 'y': // yes
461-
case 'F': // False
462-
case 'O': // On/Off
463-
case 'N': // No
464-
case 'T': // True
465-
case 'Y': // Yes
466-
return RESERVED_NAMES.contains(name);
467-
468-
// And then numbers
469-
case '0': case '1': case '2': case '3': case '4':
470-
case '5': case '6': case '7': case '8': case '9':
471-
case '-' : case '+': case '.':
472-
return true;
473-
}
474-
return false;
475-
}
476460
/*
477461
/**********************************************************
478462
/* Public API: low-level I/O
@@ -588,25 +572,25 @@ public void writeString(String text) throws IOException,JsonGenerationException
588572
return;
589573
}
590574
_verifyValueWrite("write String value");
591-
DumperOptions.ScalarStyle style;
592575

593576
// [dataformats-text#50]: Empty String always quoted
594577
if (text.isEmpty()) {
595-
style = STYLE_QUOTED;
596-
} else if (Feature.MINIMIZE_QUOTES.enabledIn(_formatFeatures)) {
597-
if (isBooleanContent(text)) {
598-
style = STYLE_QUOTED;
599-
// If this string could be interpreted as a number, it must be quoted.
600-
} else if (Feature.ALWAYS_QUOTE_NUMBERS_AS_STRINGS.enabledIn(_formatFeatures)
601-
&& PLAIN_NUMBER_P.matcher(text).matches()) {
578+
_writeScalar(text, "string", STYLE_QUOTED);
579+
return;
580+
}
581+
DumperOptions.ScalarStyle style;
582+
if (Feature.MINIMIZE_QUOTES.enabledIn(_formatFeatures)) {
583+
// If one of reserved values ("true", "null"), or, number, preserve quoting:
584+
if (_valueNeedsQuoting(text)
585+
|| (Feature.ALWAYS_QUOTE_NUMBERS_AS_STRINGS.enabledIn(_formatFeatures)
586+
&& PLAIN_NUMBER_P.matcher(text).matches())
587+
) {
602588
style = STYLE_QUOTED;
603589
} else if (text.indexOf('\n') >= 0) {
604590
style = STYLE_LITERAL;
605591
} else {
606592
style = STYLE_PLAIN;
607593
}
608-
_writeScalar(text, "string", style);
609-
return;
610594
} else {
611595
if (Feature.LITERAL_BLOCK_STYLE.enabledIn(_formatFeatures) && text.indexOf('\n') >= 0) {
612596
style = STYLE_LITERAL;
@@ -617,10 +601,6 @@ public void writeString(String text) throws IOException,JsonGenerationException
617601
_writeScalar(text, "string", style);
618602
}
619603

620-
private boolean isBooleanContent(String text) {
621-
return "true".equals(text) || "false".equals(text);
622-
}
623-
624604
@Override
625605
public void writeString(char[] text, int offset, int len) throws IOException
626606
{
@@ -944,6 +924,47 @@ private String _base64encode(final Base64Variant b64v, final byte[] input, final
944924
return sb.toString();
945925
}
946926

927+
private boolean _nameNeedsQuoting(String name) {
928+
if (name.length() == 0) { // empty String does indeed require quoting
929+
return true;
930+
}
931+
switch (name.charAt(0)) {
932+
// First, reserved name starting chars:
933+
case 'f': // false
934+
case 'o': // on/off
935+
case 'n': // no
936+
case 't': // true
937+
case 'y': // yes
938+
case 'F': // False
939+
case 'O': // On/Off
940+
case 'N': // No
941+
case 'T': // True
942+
case 'Y': // Yes
943+
return MUST_QUOTE_NAMES.contains(name);
944+
945+
// And then numbers
946+
case '0': case '1': case '2': case '3': case '4':
947+
case '5': case '6': case '7': case '8': case '9':
948+
case '-' : case '+': case '.':
949+
return true;
950+
}
951+
return false;
952+
}
953+
954+
private boolean _valueNeedsQuoting(String name) {
955+
switch (name.charAt(0)) { // caller ensures no empty String
956+
// First, reserved name starting chars:
957+
case 'f': // false
958+
case 'n': // null
959+
case 't': // true
960+
case 'F': // False/FALSE
961+
case 'N': // Null/NULL
962+
case 'T': // True/TRUE
963+
return MUST_QUOTE_VALUES.contains(name);
964+
}
965+
return false;
966+
}
967+
947968
protected String _lf() {
948969
return _outputOptions.getLineBreak().getString();
949970
}

yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/ser/GeneratorWithMinimizeTest.java

+28
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,34 @@ public void testMinimizeQuotesWithBooleanContent() throws Exception
6060
"key: true", yaml);
6161
}
6262

63+
public void testMinimizeQuotesWithNulls() throws Exception
64+
{
65+
Map<String, Object> content = new HashMap<String, Object>();
66+
content.put("key", "null");
67+
String yaml = MINIM_MAPPER.writeValueAsString(content).trim();
68+
assertEquals("---\n" +
69+
"key: \"null\"", yaml);
70+
71+
content.clear();
72+
content.put("key", "Null");
73+
yaml = MINIM_MAPPER.writeValueAsString(content).trim();
74+
assertEquals("---\n" +
75+
"key: \"Null\"", yaml);
76+
77+
content.clear();
78+
content.put("key", "NULL");
79+
yaml = MINIM_MAPPER.writeValueAsString(content).trim();
80+
assertEquals("---\n" +
81+
"key: \"NULL\"", yaml);
82+
83+
// but not for any casing
84+
content.clear();
85+
content.put("key", "nuLL");
86+
yaml = MINIM_MAPPER.writeValueAsString(content).trim();
87+
assertEquals("---\n" +
88+
"key: nuLL", yaml);
89+
}
90+
6391
public void testLiteralStringsMultiLine() throws Exception
6492
{
6593
Map<String, Object> content = new HashMap<String, Object>();

0 commit comments

Comments
 (0)