Implemented [JACKSON-208]
diff --git a/release-notes/CREDITS b/release-notes/CREDITS
index 9975666..a0d6dd3 100644
--- a/release-notes/CREDITS
+++ b/release-notes/CREDITS
@@ -240,3 +240,8 @@
* Reported [JACKSON-196], suggested fix: Schema generation does not
respect the annotation configured serializer on a bean property
[1.4.0]
+
+Mark Stevens:
+ * Requested [JACKSON-208] Aallow unquoted control characters (esp. tabs)
+ in JSON Strings and field names
+ [1.4.0]
diff --git a/release-notes/VERSION b/release-notes/VERSION
index 6b77c6f..071813e 100644
--- a/release-notes/VERSION
+++ b/release-notes/VERSION
@@ -20,6 +20,10 @@
* [JACKSON-196] Schema generation does not respect the annotation
configured serializer on a bean property
(reported by Gil M)
+ * [JACKSON-208] Add feature (JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS)
+ to allow unquoted control characters (esp. tabs) in Strings and
+ field names
+ (requested by Mark S)
New features:
diff --git a/src/java/org/codehaus/jackson/JsonParser.java b/src/java/org/codehaus/jackson/JsonParser.java
index 65bf389..c5a03f5 100644
--- a/src/java/org/codehaus/jackson/JsonParser.java
+++ b/src/java/org/codehaus/jackson/JsonParser.java
@@ -114,6 +114,25 @@
,ALLOW_SINGLE_QUOTES(false)
/**
+ * Feature that determines whether parser will allow
+ * JSON Strings to contain unquoted control characters
+ * (ascii characters with value less than 32, including
+ * tab and line feed characters) or not.
+ * If feature is set false, an exception is thrown if such a
+ * character is encountered.
+ *<p>
+ * Since JSON specification requires quoting for all
+ * control characters,
+ * this is a non-standard feature, and as such disabled by
+ * default.
+ *<p>
+ * This feature can be changed for parser instances.
+ *
+ * @since 1.4
+ */
+ ,ALLOW_UNQUOTED_CONTROL_CHARS(false)
+
+ /**
* Feature that determines whether JSON object field names are
* to be canonicalized using {@link String#intern} or not:
* if enabled, all field names will be intern()ed (and caller
diff --git a/src/java/org/codehaus/jackson/impl/JsonParserBase.java b/src/java/org/codehaus/jackson/impl/JsonParserBase.java
index 7727378..362b398 100644
--- a/src/java/org/codehaus/jackson/impl/JsonParserBase.java
+++ b/src/java/org/codehaus/jackson/impl/JsonParserBase.java
@@ -585,12 +585,22 @@
_reportError(msg);
}
+ /**
+ * Method called to report a problem with unquoted control character.
+ * Note: starting with version 1.4, it is possible to suppress
+ * exception by enabling {@link JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS}.
+ *
+ * @return If space is accepted, space itself (otherwise exception thrown)
+ */
protected void _throwUnquotedSpace(int i, String ctxtDesc)
throws JsonParseException
{
- char c = (char) i;
- String msg = "Illegal unquoted character ("+_getCharDesc(c)+"): has to be escaped using backslash to be included in "+ctxtDesc;
- _reportError(msg);
+ // JACKSON-208; possible to allow unquoted control chars:
+ if (!isEnabled(Feature.ALLOW_UNQUOTED_CONTROL_CHARS) || i >= INT_SPACE) {
+ char c = (char) i;
+ String msg = "Illegal unquoted character ("+_getCharDesc(c)+"): has to be escaped using backslash to be included in "+ctxtDesc;
+ _reportError(msg);
+ }
}
protected void _reportMismatchedEndMarker(int actCh, char expCh)
diff --git a/src/java/org/codehaus/jackson/impl/Utf8StreamParser.java b/src/java/org/codehaus/jackson/impl/Utf8StreamParser.java
index 9179843..7998284 100644
--- a/src/java/org/codehaus/jackson/impl/Utf8StreamParser.java
+++ b/src/java/org/codehaus/jackson/impl/Utf8StreamParser.java
@@ -466,12 +466,12 @@
}
// Unquoted white space?
if (ch != INT_BACKSLASH) {
+ // As per [JACKSON-208], call can now return:
_throwUnquotedSpace(ch, "name");
+ } else {
+ // Nope, escape sequence
+ ch = _decodeEscaped();
}
-
- // Nope, escape sequence
-
- ch = _decodeEscaped();
/* Oh crap. May need to UTF-8 (re-)encode it, if it's
* beyond 7-bit ascii. Gets pretty messy.
* If this happens often, may want to use different name
@@ -651,12 +651,12 @@
if (ch != INT_QUOTE && codes[ch] != 0) {
if (ch != INT_BACKSLASH) {
// Unquoted white space?
+ // As per [JACKSON-208], call can now return:
_throwUnquotedSpace(ch, "name");
+ } else {
+ // Nope, escape sequence
+ ch = _decodeEscaped();
}
-
- // Nope, escape sequence
-
- ch = _decodeEscaped();
/* Oh crap. May need to UTF-8 (re-)encode it, if it's
* beyond 7-bit ascii. Gets pretty messy.
* If this happens often, may want to use different name
@@ -974,10 +974,12 @@
break;
default:
if (c < INT_SPACE) {
+ // As per [JACKSON-208], call can now return:
_throwUnquotedSpace(c, "string value");
+ } else {
+ // Is this good enough error message?
+ _reportInvalidChar(c);
}
- // Is this good enough error message?
- _reportInvalidChar(c);
}
// Need more room?
if (outPtr >= outBuf.length) {
@@ -1046,10 +1048,12 @@
break;
default:
if (c < INT_SPACE) {
+ // As per [JACKSON-208], call can now return:
_throwUnquotedSpace(c, "string value");
+ } else {
+ // Is this good enough error message?
+ _reportInvalidChar(c);
}
- // Is this good enough error message?
- _reportInvalidChar(c);
}
}
}
diff --git a/src/mapper/java/org/codehaus/jackson/map/DeserializationContext.java b/src/mapper/java/org/codehaus/jackson/map/DeserializationContext.java
index 5e14a27..86760ba 100644
--- a/src/mapper/java/org/codehaus/jackson/map/DeserializationContext.java
+++ b/src/mapper/java/org/codehaus/jackson/map/DeserializationContext.java
@@ -27,6 +27,10 @@
public DeserializationConfig getConfig() { return _config; }
+ /**
+ * Convenience method for checking whether specified on/off
+ * feature is enabled
+ */
public boolean isEnabled(DeserializationConfig.Feature feat) {
return _config.isEnabled(feat);
}
@@ -35,6 +39,10 @@
return _config.getBase64Variant();
}
+ /**
+ * Accessor for getting access to the underlying JSON parser used
+ * for deserialization.
+ */
public abstract JsonParser getParser();
/*
@@ -45,10 +53,18 @@
/**
* Method that can be used to get access to a reusable ObjectBuffer,
- * useful for constructing Object arrays and Lists.
+ * useful for efficiently constructing Object arrays and Lists.
+ * Note that leased buffers should be returned once deserializer
+ * is done, to allow for reuse during same round of deserialization.
*/
public abstract ObjectBuffer leaseObjectBuffer();
+ /**
+ * Method to call to return object buffer previously leased with
+ * {@link #leaseObjectBuffer}.
+ *
+ * @param buf Returned object buffer
+ */
public abstract void returnObjectBuffer(ObjectBuffer buf);
/**
@@ -74,15 +90,42 @@
//////////////////////////////////////////////////////////////
*/
+ /**
+ * Helper method for constructing generic mapping exception for specified type
+ */
public abstract JsonMappingException mappingException(Class<?> targetClass);
+
+ /**
+ * Helper method for constructing instantiation exception for specified type,
+ * to indicate problem with physically constructing instance of
+ * specified class (missing constructor, exception from constructor)
+ */
public abstract JsonMappingException instantiationException(Class<?> instClass, Exception e);
+ /**
+ * Helper method for constructing exception to indicate that input JSON
+ * String was not in recognized format for deserializing into given type.
+ */
public abstract JsonMappingException weirdStringException(Class<?> instClass, String msg);
+
+ /**
+ * Helper method for constructing exception to indicate that input JSON
+ * Number was not suitable for deserializing into given type.
+ */
public abstract JsonMappingException weirdNumberException(Class<?> instClass, String msg);
+ /**
+ * Helper method for constructing exception to indicate that given JSON
+ * Object field name was not in format to be able to deserialize specified
+ * key type.
+ */
public abstract JsonMappingException weirdKeyException(Class<?> keyClass, String keyValue, String msg);
/**
+ * Helper method for constructing exception to indicate that JSON Object
+ * field name did not map to a known property of type being
+ * deserialized.
+ *
* @param instanceOrClass Either value being populated (if one has been
* instantiated), or Class that indicates type that would be (or
* have been) instantiated
diff --git a/src/mapper/java/org/codehaus/jackson/map/ext/CoreXMLDeserializers.java b/src/mapper/java/org/codehaus/jackson/map/ext/CoreXMLDeserializers.java
index fe9f170..33ea44f 100644
--- a/src/mapper/java/org/codehaus/jackson/map/ext/CoreXMLDeserializers.java
+++ b/src/mapper/java/org/codehaus/jackson/map/ext/CoreXMLDeserializers.java
@@ -116,7 +116,7 @@
final static DocumentBuilderFactory _parserFactory;
static {
_parserFactory = DocumentBuilderFactory.newInstance();
- // yup, only cavemen do XML without recognizing namespaces...
+ // yup, only cave men do XML without recognizing namespaces...
_parserFactory.setNamespaceAware(true);
}
diff --git a/src/test/main/BaseTest.java b/src/test/main/BaseTest.java
index 5dafab2..5d6769d 100644
--- a/src/test/main/BaseTest.java
+++ b/src/test/main/BaseTest.java
@@ -254,6 +254,12 @@
return str;
}
+ /*
+ ////////////////////////////////////////////////////////
+ // And other helpers
+ ////////////////////////////////////////////////////////
+ */
+
protected byte[] encodeInUTF32BE(String input)
{
int len = input.length();
@@ -267,4 +273,8 @@
}
return result;
}
+
+ public String quote(String str) {
+ return '"'+str+'"';
+ }
}
diff --git a/src/test/org/codehaus/jackson/main/TestJsonParser.java b/src/test/org/codehaus/jackson/main/TestJsonParser.java
index 5983ff3..d55b12c 100644
--- a/src/test/org/codehaus/jackson/main/TestJsonParser.java
+++ b/src/test/org/codehaus/jackson/main/TestJsonParser.java
@@ -25,7 +25,6 @@
jp.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false);
assertFalse(jp.isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));
- // default should be true here:
assertTrue(jp.isEnabled(JsonParser.Feature.INTERN_FIELD_NAMES));
jp.configure(JsonParser.Feature.INTERN_FIELD_NAMES, false);
assertFalse(jp.isEnabled(JsonParser.Feature.INTERN_FIELD_NAMES));
diff --git a/src/test/org/codehaus/jackson/main/TestParserFeatures.java b/src/test/org/codehaus/jackson/main/TestParserFeatures.java
index ac2e94d..0c75e1d 100644
--- a/src/test/org/codehaus/jackson/main/TestParserFeatures.java
+++ b/src/test/org/codehaus/jackson/main/TestParserFeatures.java
@@ -15,6 +15,8 @@
assertTrue(f.isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));
assertFalse(f.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
assertFalse(f.isEnabled(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES));
+ assertFalse(f.isEnabled(JsonParser.Feature.ALLOW_SINGLE_QUOTES));
+ assertFalse(f.isEnabled(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS));
}
public void testQuotesRequired() throws Exception
@@ -49,6 +51,20 @@
_testSingleQuotesEnabled(true);
}
+ // // Tests for [JACKSON-208], unquoted tabs:
+
+ public void testTabsDefault() throws Exception
+ {
+ _testTabsDefault(false);
+ _testTabsDefault(true);
+ }
+
+ public void testTabsEnabled() throws Exception
+ {
+ _testTabsEnabled(false);
+ _testTabsEnabled(true);
+ }
+
/*
/////////////////////////////////////////////////////////////////
// Secondary test methods
@@ -156,6 +172,7 @@
assertToken(JsonToken.START_ARRAY, jp.nextToken());
try {
jp.nextToken();
+ fail("Expected exception");
} catch (JsonParseException e) {
verifyException(e, "Unexpected character ('''");
}
@@ -166,6 +183,7 @@
assertToken(JsonToken.START_OBJECT, jp.nextToken());
try {
jp.nextToken();
+ fail("Expected exception");
} catch (JsonParseException e) {
verifyException(e, "Unexpected character ('''");
}
@@ -211,4 +229,40 @@
assertToken(JsonToken.END_OBJECT, jp.nextToken());
}
+
+ // // // Tests for [JACKSON-208]
+
+ private void _testTabsDefault(boolean useStream) throws Exception
+ {
+ JsonFactory f = new JsonFactory();
+ // First, let's see that by default unquoted tabs are illegal
+ String JSON = "[\"tab:\t\"]";
+ JsonParser jp = useStream ? createParserUsingStream(f, JSON, "UTF-8") : createParserUsingReader(f, JSON);
+ assertToken(JsonToken.START_ARRAY, jp.nextToken());
+ try {
+ jp.nextToken();
+ jp.getText();
+ fail("Expected exception");
+ } catch (JsonParseException e) {
+ verifyException(e, "Illegal unquoted character");
+ }
+ }
+
+ private void _testTabsEnabled(boolean useStream) throws Exception
+ {
+ JsonFactory f = new JsonFactory();
+ f.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
+
+ String FIELD = "a\tb";
+ String VALUE = "\t";
+ String JSON = "{ "+quote(FIELD)+" : "+quote(VALUE)+"}";
+ JsonParser jp = useStream ? createParserUsingStream(f, JSON, "UTF-8") : createParserUsingReader(f, JSON);
+
+ assertToken(JsonToken.START_OBJECT, jp.nextToken());
+ assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+ assertEquals(FIELD, jp.getText());
+ assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+ assertEquals(VALUE, jp.getText());
+ assertToken(JsonToken.END_OBJECT, jp.nextToken());
+ }
}
diff --git a/src/test/org/codehaus/jackson/map/BaseMapTest.java b/src/test/org/codehaus/jackson/map/BaseMapTest.java
index 964f2af..6df79af 100644
--- a/src/test/org/codehaus/jackson/map/BaseMapTest.java
+++ b/src/test/org/codehaus/jackson/map/BaseMapTest.java
@@ -117,11 +117,4 @@
{
return serializeAsString(new ObjectMapper(), value);
}
-
- public String quote(String str) {
- return '"'+str+'"';
- }
}
-
-
-