Skip to content

Commit e996c51

Browse files
committed
Fixed #229 (add StringQuotingChecker to configure quoting aspects of keys, String values)
1 parent 860a883 commit e996c51

File tree

7 files changed

+240
-65
lines changed

7 files changed

+240
-65
lines changed

release-notes/VERSION-2.x

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Modules:
1717
(reported by wkwkhautbois@github)
1818
#226: Quote 'y'/'Y'/'n'/'N' as names too (to avoid problems with Boolean keys)
1919
(requested by pnepywoda@github)
20+
#229: Allow configuring the way "must quote" is determined for property names, String values
2021
- SnakeYAML 1.26 -> 1.27
2122
- Add Gradle Module Metadata (https://blog.gradle.org/alignment-with-gradle-module-metadata)
2223

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ protected YAMLParser _createParser(byte[] data, int offset, int len, IOContext c
490490
protected YAMLGenerator _createGenerator(Writer out, IOContext ctxt) throws IOException {
491491
int feats = _yamlGeneratorFeatures;
492492
YAMLGenerator gen = new YAMLGenerator(ctxt, _generatorFeatures, feats,
493-
_objectCodec, out, _version);
493+
_quotingChecker, _objectCodec, out, _version);
494494
// any other initializations? No?
495495
return gen;
496496
}

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

+13-1
Original file line numberDiff line numberDiff line change
@@ -230,21 +230,25 @@ private Feature(boolean defaultState) {
230230

231231
protected int _rootValueCount;
232232

233-
protected final StringQuotingChecker _quotingChecker = StringQuotingChecker.Default.instance();
233+
protected final StringQuotingChecker _quotingChecker;
234+
234235
/*
235236
/**********************************************************
236237
/* Life-cycle
237238
/**********************************************************
238239
*/
239240

240241
public YAMLGenerator(IOContext ctxt, int jsonFeatures, int yamlFeatures,
242+
StringQuotingChecker quotingChecker,
241243
ObjectCodec codec, Writer out,
242244
org.yaml.snakeyaml.DumperOptions.Version version)
243245
throws IOException
244246
{
245247
super(jsonFeatures, codec);
246248
_ioContext = ctxt;
247249
_formatFeatures = yamlFeatures;
250+
_quotingChecker = (quotingChecker == null)
251+
? StringQuotingChecker.Default.instance() : quotingChecker;
248252
_writer = out;
249253
_docVersion = version;
250254

@@ -256,6 +260,14 @@ public YAMLGenerator(IOContext ctxt, int jsonFeatures, int yamlFeatures,
256260
_emitStartDocument();
257261
}
258262

263+
@Deprecated // since 2.12
264+
public YAMLGenerator(IOContext ctxt, int jsonFeatures, int yamlFeatures,
265+
ObjectCodec codec, Writer out,
266+
org.yaml.snakeyaml.DumperOptions.Version version) throws IOException {
267+
this(ctxt, jsonFeatures, yamlFeatures, null,
268+
codec, out, version);
269+
}
270+
259271
protected DumperOptions buildDumperOptions(int jsonFeatures, int yamlFeatures,
260272
org.yaml.snakeyaml.DumperOptions.Version version)
261273
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
Main abstractions for Jackson YAML format backend, including
3+
streaming reader (({@link com.fasterxml.jackson.dataformat.yaml.YAMLParser}),
4+
writer ({@link com.fasterxml.jackson.dataformat.yaml.YAMLGenerator})
5+
(and factory to create them, {@link com.fasterxml.jackson.dataformat.yaml.YAMLFactory})
6+
as well as mapper ({@link com.fasterxml.jackson.dataformat.yaml.YAMLMapper}).
7+
*/
8+
9+
package com.fasterxml.jackson.dataformat.yaml;

yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/util/StringQuotingChecker.java

+88-63
Original file line numberDiff line numberDiff line change
@@ -18,33 +18,24 @@ public abstract class StringQuotingChecker
1818
{
1919
private static final long serialVersionUID = 1L;
2020

21-
/* As per <a href="https://yaml.org/type/bool.html">YAML Spec</a> there are a few
22-
* aliases for booleans, and we better quote such values as keys; although Jackson
23-
* itself has no problems dealing with them, some other tools do have.
24-
*/
25-
// 02-Apr-2019, tatu: Some names will look funny if escaped: let's leave out
26-
// single letter case (esp so 'y' won't get escaped)
27-
// 17-Sep-2020, tatu: [dataformats-text#226] No, let's be consistent w/ values
28-
private final static Set<String> DEFAULT_QUOTABLE_NAMES = new HashSet<>(Arrays.asList(
29-
"y", "Y", "n", "N",
30-
"yes", "Yes", "YES", "no", "No", "NO",
31-
"true", "True", "TRUE", "false", "False", "FALSE",
32-
"on", "On", "ON", "off", "Off", "OFF",
33-
"null", "Null", "NULL"
34-
));
35-
3621
/**
3722
* As per YAML <a href="https://yaml.org/type/null.html">null</a>
3823
* and <a href="https://yaml.org/type/bool.html">boolean</a> type specs,
39-
* better retain quoting for some values
24+
* better retain quoting for some keys (property names) and values.
4025
*/
41-
private final static Set<String> DEFAULT_QUOTABLE_VALUES = new HashSet<>(Arrays.asList(
26+
private final static Set<String> RESERVED_KEYWORDS = new HashSet<>(Arrays.asList(
27+
// 02-Apr-2019, tatu: Some names will look funny if escaped: let's leave out
28+
// single letter case (esp so 'y' won't get escaped)
29+
// 17-Sep-2020, tatu: [dataformats-text#226] No, let's be consistent w/ values
4230
"false", "False", "FALSE",
31+
"n", "N",
32+
"no", "No", "NO",
4333
"null", "Null", "NULL",
44-
"on", "On", "ON", "off", "Off", "OFF",
34+
"on", "On", "ON",
35+
"off", "Off", "OFF",
4536
"true", "True", "TRUE",
46-
"y", "Y", "n", "N",
47-
"yes", "Yes", "YES", "no", "No", "NO"
37+
"y", "Y",
38+
"yes", "Yes", "YES"
4839
));
4940

5041
/**
@@ -53,48 +44,45 @@ public abstract class StringQuotingChecker
5344
* to check whether given property name should be quoted: usually
5445
* to prevent it from being read as non-String key (boolean or number)
5546
*/
56-
public boolean needToQuoteName(String name)
57-
{
58-
// empty String does indeed require quoting
59-
if (name.length() == 0) {
60-
return true;
61-
}
62-
switch (name.charAt(0)) {
63-
// First, reserved name starting chars:
64-
case 'f': // false
65-
case 'n': // no/n/null
66-
case 'o': // on/off
67-
case 't': // true
68-
case 'y': // yes/y
69-
case 'F': // False
70-
case 'N': // No/N/Null
71-
case 'O': // On/Off
72-
case 'T': // True
73-
case 'Y': // Yes/Y
74-
return isDefaultQuotableName(name);
75-
76-
// And then numbers
77-
case '0': case '1': case '2': case '3': case '4':
78-
case '5': case '6': case '7': case '8': case '9':
79-
case '-' : case '+': case '.':
80-
return true;
81-
}
82-
return false;
83-
}
47+
public abstract boolean needToQuoteName(String name);
8448

8549
/**
8650
* Method called by
8751
* {@link com.fasterxml.jackson.dataformat.yaml.YAMLGenerator}
8852
* to check whether given String value should be quoted: usually
89-
* to prevent it from being value of different type (boolean or number)
53+
* to prevent it from being value of different type (boolean or number).
9054
*/
91-
public boolean needToQuoteValue(String value)
92-
{
93-
// empty String does indeed require quoting
55+
public abstract boolean needToQuoteValue(String value);
56+
57+
/**
58+
* Helper method that sub-classes may use to see if given String value is
59+
* one of:
60+
*<ul>
61+
* <li>YAML 1.1 keyword representing
62+
* <a href="https://yaml.org/type/bool.html">boolean</a>
63+
* </li>
64+
* <li>YAML 1.1 keyword representing
65+
* <a href="https://yaml.org/type/null.html">null</a> value
66+
* </li>
67+
* <li>empty String (length 0)
68+
* </li>
69+
*</li>
70+
* and returns {@code true} if so.
71+
*
72+
* @param value String to check
73+
*
74+
* @return {@code true} if given value is a Boolean or Null representation
75+
* (as per YAML 1.1 specification) or empty String
76+
*/
77+
protected boolean isReservedKeyword(String value) {
9478
if (value.length() == 0) {
9579
return true;
9680
}
97-
switch (value.charAt(0)) {
81+
return _isReservedKeyword(value.charAt(0), value);
82+
}
83+
84+
protected boolean _isReservedKeyword(int firstChar, String name) {
85+
switch (firstChar) {
9886
// First, reserved name starting chars:
9987
case 'f': // false
10088
case 'n': // no/n/null
@@ -106,20 +94,32 @@ public boolean needToQuoteValue(String value)
10694
case 'O': // On/Off
10795
case 'T': // True
10896
case 'Y': // Yes/Y
109-
if (isDefaultQuotableValue(value)) {
110-
return true;
111-
}
112-
break;
97+
return RESERVED_KEYWORDS.contains(name);
11398
}
114-
return valueHasQuotableChar(value);
99+
return false;
115100
}
116101

117-
protected boolean isDefaultQuotableName(String name) {
118-
return DEFAULT_QUOTABLE_NAMES.contains(name);
102+
/**
103+
* Helper method that sub-classes may use to see if given String value
104+
* looks like a YAML 1.1 numeric value and would likely be considered
105+
* a number when parsing unless quoting is used.
106+
*/
107+
protected boolean looksLikeYAMLNumber(String name) {
108+
if (name.length() > 0) {
109+
return _looksLikeYAMLNumber(name.charAt(0), name);
110+
}
111+
return false;
119112
}
120113

121-
protected boolean isDefaultQuotableValue(String name) {
122-
return DEFAULT_QUOTABLE_VALUES.contains(name);
114+
protected boolean _looksLikeYAMLNumber(int firstChar, String name) {
115+
switch (firstChar) {
116+
// And then numbers
117+
case '0': case '1': case '2': case '3': case '4':
118+
case '5': case '6': case '7': case '8': case '9':
119+
case '-' : case '+': case '.':
120+
return true;
121+
}
122+
return false;
123123
}
124124

125125
/**
@@ -166,7 +166,7 @@ protected boolean valueHasQuotableChar(String inputStr)
166166
* Default {@link StringQuotingChecker} implementation used unless
167167
* custom implementation registered.
168168
*/
169-
public final static class Default
169+
public static class Default
170170
extends StringQuotingChecker
171171
implements java.io.Serializable
172172
{
@@ -177,5 +177,30 @@ public final static class Default
177177
public Default() { }
178178

179179
public static Default instance() { return INSTANCE; }
180+
181+
/**
182+
* Default implementation will call
183+
* {@link #isReservedKeyword(String)} and
184+
* {@link #looksLikeYAMLNumber(String)} to determine
185+
* if quoting should be applied.
186+
*/
187+
@Override
188+
public boolean needToQuoteName(String name)
189+
{
190+
return isReservedKeyword(name) || looksLikeYAMLNumber(name);
191+
}
192+
193+
/**
194+
* Default implementation will call
195+
* {@link #isReservedKeyword(String)}
196+
* and {@link #valueHasQuotableChar(String)} to determine
197+
* if quoting should be applied.
198+
*/
199+
@Override
200+
public boolean needToQuoteValue(String value)
201+
{
202+
// Only consider reserved keywords but not numbers?
203+
return isReservedKeyword(value) || valueHasQuotableChar(value);
204+
}
180205
}
181206
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
Helper classes for YAML backend.
3+
4+
@since 2.12
5+
*/
6+
7+
package com.fasterxml.jackson.dataformat.yaml.util;

0 commit comments

Comments
 (0)