Skip to content
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

Add Feature for generating JSONP compliant output #136

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/main/java/com/fasterxml/jackson/core/JsonGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,17 @@ public enum Feature {
* @since 2.3
*/
STRICT_DUPLICATE_DETECTION(false),

/**
* Feature that specifies that Unicode newline characters U+2028
* and U+2029 must be explicitly escaped in JSON output. This
* ensures that the JSON output can be used as JSONP.
*<p>
* Feature is disabled by default.
*
* @since 2.4
*/
JSONP_COMPLIANT(false),
;

private final boolean _defaultState;
Expand Down Expand Up @@ -430,6 +441,10 @@ public PrettyPrinter getPrettyPrinter() {
*/
public int getHighestEscapedChar() { return 0; }

public JsonGenerator setJsonpCompliantOutput(boolean escape) { return this; }

public boolean getJsonpCompliantOutput() { return false; }

/**
* Method for accessing custom escapes factory uses for {@link JsonGenerator}s
* it creates.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ public JsonGenerator enable(Feature f) {
_cfgNumbersAsStrings = true;
} else if (f == Feature.ESCAPE_NON_ASCII) {
setHighestNonEscapedChar(127);
} else if (f == Feature.JSONP_COMPLIANT) {
setJsonpCompliantOutput(true);
}
return this;
}
Expand All @@ -102,6 +104,8 @@ public JsonGenerator disable(Feature f) {
_cfgNumbersAsStrings = false;
} else if (f == Feature.ESCAPE_NON_ASCII) {
setHighestNonEscapedChar(0);
} else if (f == Feature.JSONP_COMPLIANT) {
setJsonpCompliantOutput(false);
}
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ public JsonGeneratorImpl(IOContext ctxt, int features, ObjectCodec codec)
if (isEnabled(Feature.ESCAPE_NON_ASCII)) {
setHighestNonEscapedChar(127);
}
if (isEnabled(Feature.JSONP_COMPLIANT)) {
setJsonpCompliantOutput(true);
}
}

/*
Expand All @@ -116,6 +119,22 @@ public int getHighestEscapedChar() {
return _maximumNonEscapedChar;
}

@Override
public JsonGenerator setJsonpCompliantOutput(boolean escape) {
if (escape) {
_characterEscapes = new JsonpCharacterEscapes();
} else if (getJsonpCompliantOutput()) {
_characterEscapes = null;
}

return this;
}

@Override
public boolean getJsonpCompliantOutput() {
return _characterEscapes instanceof JsonpCharacterEscapes;
}

@Override
public JsonGenerator setCharacterEscapes(CharacterEscapes esc)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.fasterxml.jackson.core.json;

import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.io.CharacterEscapes;
import com.fasterxml.jackson.core.io.SerializedString;

/**
* Character escapes for producing json that can be safely used for JSONP.
* Used when {@link com.fasterxml.jackson.core.JsonGenerator.Feature#JSONP_COMPLIANT}
* is enabled.
*/
public class JsonpCharacterEscapes extends CharacterEscapes
{
private static final int[] asciiEscapes = CharacterEscapes.standardAsciiEscapesForJSON();
private static final SerializedString escapeFor2028 = new SerializedString("\\u2028");
private static final SerializedString escapeFor2029 = new SerializedString("\\u2029");

@Override
public SerializableString getEscapeSequence(int ch)
{
switch (ch) {
case 0x2028:
return escapeFor2028;
case 0x2029:
return escapeFor2029;
default:
return null;
}
}

@Override
public int[] getEscapeCodesForAscii()
{
return asciiEscapes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,22 @@ public void testCopy() throws Exception
assertTrue(jf.isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));
assertFalse(jf.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
assertFalse(jf.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII));
assertFalse(jf.isEnabled(JsonGenerator.Feature.JSONP_COMPLIANT));
jf.disable(JsonFactory.Feature.INTERN_FIELD_NAMES);
jf.enable(JsonParser.Feature.ALLOW_COMMENTS);
jf.enable(JsonGenerator.Feature.ESCAPE_NON_ASCII);
jf.enable(JsonGenerator.Feature.JSONP_COMPLIANT);
// then change, verify that changes "stick"
assertFalse(jf.isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));
assertTrue(jf.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
assertTrue(jf.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII));
assertTrue(jf.isEnabled(JsonGenerator.Feature.JSONP_COMPLIANT));

JsonFactory jf2 = jf.copy();
assertFalse(jf2.isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));
assertTrue(jf.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
assertTrue(jf.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII));
assertTrue(jf.isEnabled(JsonGenerator.Feature.JSONP_COMPLIANT));
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,23 @@ public void testBigDecimalAsPlain() throws IOException
jg.close();
assertEquals("100", sw.toString());
}

public void testJsonpCompliantOutput() throws IOException
{
JsonFactory jf = new JsonFactory();
// by default, escaping should be disabled
_testJsonpCompliantEscaping(jf, false);
// can enable it
jf.enable(JsonGenerator.Feature.JSONP_COMPLIANT);
_testJsonpCompliantEscaping(jf, true);
// and (re)disable:
jf.disable(JsonGenerator.Feature.JSONP_COMPLIANT);
_testJsonpCompliantEscaping(jf, false);
}

/**
* Testing for generating JSONP compliant output.
*/

private String _writeNumbers(JsonFactory jf) throws IOException
{
Expand Down Expand Up @@ -150,4 +167,22 @@ private void _testNonNumericQuoting(JsonFactory jf, boolean quoted)
assertEquals("{\"double\":NaN} {\"float\":NaN}", result);
}
}

private void _testJsonpCompliantEscaping(JsonFactory jf, boolean escaped)
throws IOException
{
StringWriter sw = new StringWriter();
JsonGenerator jg = jf.createGenerator(sw);
jg.writeStartObject();
jg.writeStringField("str", "foo\u2028bar\u2029");
jg.writeEndObject();
jg.close();

String result = sw.toString();
if (escaped) {
assertEquals("{\"str\":\"foo\\u2028bar\\u2029\"}", result);
} else {
assertEquals("{\"str\":\"foo\u2028bar\u2029\"}", result);
}
}
}