Skip to content

Allow BeanPropertyWriter Sub-classes to Override get() (remove final) #3343

@alzimmermsft

Description

@alzimmermsft

Is your feature request related to a problem? Please describe.

In BeanPropertyWriter the get method is final and a few of the serialization methods inline the call to get making it much more difficult to create sub-classes of BeanPropertyWriter that attempt to optimize the get operation. An example of this can be seen in the Blackbird module which requires the entire serializeAsField method to be overloaded (https://github.com/FasterXML/jackson-modules-base/blob/2.14/blackbird/src/main/java/com/fasterxml/jackson/module/blackbird/ser/ObjectPropertyWriter.java#L45) which then enforces additional constraints that the overload needs to handle complete serialization.

Describe the solution you'd like

I think it would be useful for sub-classes of BeanPropertyWriter to be able to override get functionality and allow BeanPropertyWriter to handle serialization based on its configuration (if serialization isn't also overridden). This would allow for further enhancements to the Blackbird module where it could become a simple optimized value getter using MethodHandle based reflection and pass the retrieved back to the BeanPropertyWriter. Overall, this would enable Blackbird to remove its restrictions on which types it is allowed to handle.

Usage example

BeanPropertyWriter.java

public void serializeAsField(Object bean, JsonGenerator gen,
        SerializerProvider prov) throws Exception {
    final Object value = getValue(bean);

    // Null handling is bit different, check that first
    if (value == null) {
        if (_nullSerializer != null) {
            gen.writeFieldName(_name);
            _nullSerializer.serialize(null, gen, prov);
        }
        return;
    }
    // then find serializer to use
    JsonSerializer<Object> ser = _serializer;
    if (ser == null) {
        Class<?> cls = value.getClass();
        PropertySerializerMap m = _dynamicSerializers;
        ser = m.serializerFor(cls);
        if (ser == null) {
            ser = _findAndAddDynamic(m, cls, prov);
        }
    }
    // and then see if we must suppress certain values (default, empty)
    if (_suppressableValue != null) {
        if (MARKER_FOR_EMPTY == _suppressableValue) {
            if (ser.isEmpty(prov, value)) {
                return;
            }
        } else if (_suppressableValue.equals(value)) {
            return;
        }
    }
    // For non-nulls: simple check for direct cycles
    if (value == bean) {
        // four choices: exception; handled by call; pass-through or write null
        if (_handleSelfReference(bean, gen, prov, ser)) {
            return;
        }
    }
    gen.writeFieldName(_name);
    if (_typeSerializer == null) {
        ser.serialize(value, gen, prov);
    } else {
        ser.serializeWithType(value, gen, prov, _typeSerializer);
    }
}

// Basically the same as the existing get() as this changes the scope from "public final" to "protected"
protected Object getValue(Object bean) throws Exception {
    return get(bean);
}

Blackbird Optimized Getters

@Override
protected Object getValue(Object bean) throws Exception {
  // Insert optimized MethodHandle/Callsite retrieval here
}

Metadata

Metadata

Assignees

Labels

2.19Issues planned at 2.19 or later

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions