7

We need to process some broken JSON from a legacy server here that wrongly encodes null values as literal "null" strings in its output.

I already found that I probably want to override https://github.com/FasterXML/jackson-core/blob/master/src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java#L368 to "fix" this, but this seems to be so deep inside Jackson that I'd rather do it differently. Are there alternatives, for example by using the ObjectMapper to add a custom deserializer for the String.class or am I lost?

Thomas Keller
  • 5,933
  • 6
  • 48
  • 80

2 Answers2

2

Ok, it worked by overriding the standard String deserializer. Unfortunately I had to copy the complete implementation over because org/codehaus/jackson/map/deser/std/StringDeserializer.java is final and cannot be extended.

public class FixesModule extends SimpleModule {

    public FixesModule() {
        super();
        addDeserializer(String.class, new CustomStringDeserializer());
    }
}

and

public class CustomStringDeserializer extends StdScalarDeserializer<String> {

    private static final String NULL_STRING = "null";

    public CustomStringDeserializer() {
        super(String.class);
    }

    @Override
    public String deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        JsonToken curr = jp.getCurrentToken();
        // Usually should just get string value:
        if (curr == JsonToken.VALUE_STRING) {
            // BEGIN NULL_STRING fix
            if (NULL_STRING.equals(jp.getText())) {
                return null;
            }
            // END NULL_STRING fix
            return jp.getText();
        }
        // [JACKSON-330]: need to gracefully handle byte[] data, as base64
        if (curr == JsonToken.VALUE_EMBEDDED_OBJECT) {
            Object ob = jp.getEmbeddedObject();
            if (ob == null) {
                return null;
            }
            if (ob instanceof byte[]) {
                return Base64Variants.getDefaultVariant().encode((byte[]) ob, false);
            }
            // otherwise, try conversion using toString()...
            return ob.toString();
        }
        // Can deserialize any scalar value, but not markers
        if (curr.isScalarValue()) {
            return jp.getText();
        }
        throw ctxt.mappingException(_valueClass, curr);
    }

    // 1.6: since we can never have type info ("natural type"; String, Boolean,
    // Integer, Double):
    // (is it an error to even call this version?)
    @Override
    public String deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer)
            throws IOException, JsonProcessingException {
        return deserialize(jp, ctxt);
    }
}
P44T
  • 1,109
  • 12
  • 21
Thomas Keller
  • 5,933
  • 6
  • 48
  • 80
  • 2
    There's no need to copy the entire implementation, because you can use composition. Just check for the `null` string first and then delegate to `StringDeserializer.instance.deserialize(...);`. – P44T May 13 '19 at 13:33
0

due to StringDeserializer is Final, we can use another way to process String

use StringDeserializer.instance.deserialize()

here the simple to override string deserializer, and parse null as empty string

public class NullStringDeserializer extends JsonDeserializer<String> {

@Override
public String deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
    String s = StringDeserializer.instance.deserialize(jsonParser, deserializationContext);
    return s == null ? "" : s;
}

}
Procrastinator
  • 2,526
  • 30
  • 27
  • 36
hhyygg2009
  • 33
  • 7