Search code examples
javajsonserializationjacksonjava-custom-serialization

Custom BeanPropertyFilter - only serializing part of string


I am in dire need of help. I am currently making some security restrictions on a resource in a Content API, where i need to either: Include properties, Truncate properties (if they are String.class) or remove properties from the object of serialization, based on a SecurityContext.

Im using Jackson as my json serializer, and have chosen to use a BeanPropertyFilter to check if each property should be serialized or not.

Now the remove, and include options are pretty straight forward, either i serialize the bean or not.

But the truncate option is a little worse. So here's what ive done so far regarding the serialization of the property.

@Override
public void serializeAsField(Object bean, JsonGenerator jgen, SerializerProvider provider, BeanPropertyWriter writer) throws Exception {
    switch (determineFilterAction(writer)) {
        case INCLUDE_UNMODIFIED:
            writer.serializeAsField(bean, jgen, provider);
            break;
        case INCLUDE_TRUNCATED:
            Object value = writer.get(bean);
            if (!(value instanceof String)) {
                throw new UnsupportedOperationException("Annotation indicates truncate on " + writer.toString() + " which is not a string, but a " + value.getClass().getSimpleName() + " and is unsupported");
            }
            JsonSerializer oldSerialzer= writer.getSerializer();
            writer.assignSerializer(new TruncateStringJsonSerializer());
            writer.serializeAsField(bean, jgen, provider);
            writer.assignSerializer(oldSerialzer);
            break;
        case REMOVE:
            // do nothing
            break;
    }
}

Problem is, that there's a guard in the BeanPropertyWriter.assignSerializer() method, that will not allow me to switch in a new custom serializer for that one property.

Is there any way of hooking into, and modifying the bean-value? Or will i have to override Factories, and BeanPropertyWriter in order to override the assignSerializer() method (to avoid the serializer guard)?

I cant just annotate my properties with a special writer, since the object of serialization doesnt know anything about the SecurityContext, so there is no way to inject the securityContext into a custom serializer.

Is there any way at all to make this truncate option happen (without making it too hackish)?

Thanks in advance.

//Best regards, Martin.

UPDATE

Based on StaxMan's answer, i have updated the method to look like this, and it seems to work. At least all my tests run correctly. Will test further on development application-servers.

@Override
public void serializeAsField(Object bean, JsonGenerator jgen, SerializerProvider provider, BeanPropertyWriter writer) throws Exception {
    switch (determineFilterAction(writer)) {
        case INCLUDE_UNMODIFIED:
            writer.serializeAsField(bean, jgen, provider);
            break;
        case INCLUDE_TRUNCATED:
            Object value = writer.get(bean);
            if (!(value instanceof String)) {
                throw new UnsupportedOperationException("Annotation indicates truncate on " + writer.toString() + " which is not a string, but a " + value.getClass().getSimpleName() + " and is unsupported");
            }
            String valueString = (String) value;
            jgen.writeFieldName(writer.getName());
            jgen.writeString(StringUtils.abbreviate(valueString, MAX_LENGTH));
            break;
        case REMOVE:
            // do nothing
            break;
    }
}

Solution

  • No, do not even try to call assignSerializer: that is a bad idea in multi-threaded environment -- there is just a single instance of that serializer, and even if you could call it, it'd just cause you a bizarre exception in cases where two serializations were done concurrently.

    But perhaps you should consider approaching this from different direction: since you are already finding String value, why not directly write truncated value using JsonGenerator? Or, if you prefer, delegate to something that does the write; but since you are calling it, it need not be a JsonSerializer or anything.