Skip to content

JacksonSampleQuoteChars

Paul Brown edited this page Oct 19, 2011 · 1 revision

Custom quoting of JSON String characters

(contributed by Boris Granveaud)

Original problem

"I've got a problem in my GWT application which doesn't parse the JSON string produced by Jackson when it contains some Unicode characters (bug encountered with \u2028). I'm using GWT native JSON decoder com.google.gwt.json.client.JSONParser, so I guess it calls browser implementation (Chrome in my case). "

Approaches

Although currently (Jackson 1.5) there is no way to directly define which additional characters to escape, there are multiple customization points that could be used. For example:

  • Override writeString() calls of JsonGenerator to do escaping. Requires sub-classing of JsonFactory to produce specialized generator instances
  • Implement custom java.io.Writer that escapes specific characters (as long as they can never occur as legal JSON identifiers), and pass such Writer when constructing JsonGenerator
  • Add custom serializer for java.lang.String

Example code below takes the last approach (NOTE: this does not necessarily handle all types that produce JSON Strings -- for example, JsonNode does not call String serializer currently).

Custom serializer for Quoting

And here is one possible way of ensuring that only Ascii characters are output without escape mechanism:

ObjectMapper mapper = new ObjectMapper();
CustomSerializerFactory serializerFactory = new CustomSerializerFactory();
serializerFactory.addSpecificMapping(String.class, new JsonSerializer<String>() {
  final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
  final int[] ESCAPE_CODES = CharTypes.getOutputEscapes();

  private void writeUnicodeEscape(JsonGenerator gen, char c) throws IOException {
    gen.writeRaw('\\');
    gen.writeRaw('u');
    gen.writeRaw(HEX_CHARS[(c >> 12) & 0xF]);
    gen.writeRaw(HEX_CHARS[(c >> 8) & 0xF]);
    gen.writeRaw(HEX_CHARS[(c >> 4) & 0xF]);
    gen.writeRaw(HEX_CHARS[c & 0xF]);
  }

  private void writeShortEscape(JsonGenerator gen, char c) throws IOException {
    gen.writeRaw('\\');
    gen.writeRaw(c);
  }

  @Override
  public void serialize(String str, JsonGenerator gen, SerializerProvider provider) throws IOException {
    int status = ((JsonWriteContext) gen.getOutputContext()).writeValue();
    switch (status) {
      case JsonWriteContext.STATUS_OK_AFTER_COLON:
        gen.writeRaw(':');
        break;
      case JsonWriteContext.STATUS_OK_AFTER_COMMA:
        gen.writeRaw(',');
        break;
      case JsonWriteContext.STATUS_EXPECT_NAME:
        throw new JsonGenerationException("Can not write string value here");
    }
    gen.writeRaw('"');
    for (char c : str.toCharArray()) {
      if (c >= 0x80) writeUnicodeEscape(gen, c); // use generic escaping for all non US-ASCII characters
      else {
        // use escape table for first 128 characters
        int code = (c < ESCAPE_CODES.length ? ESCAPE_CODES[c] : 0);
        if (code == 0) gen.writeRaw(c); // no escaping
        else if (code < 0) writeUnicodeEscape(gen, (char) (-code - 1)); // generic escaping
        else writeShortEscape(gen, (char) code); // short escaping (\n \t ...)
      }
    }
    gen.writeRaw('"');
  }
});
mapper.setSerializerFactory(serializerFactory);

CategoryJackson