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

Change serializer applied to a single property of an class #4385

Open
Guy2You opened this issue Feb 14, 2024 · 4 comments
Open

Change serializer applied to a single property of an class #4385

Guy2You opened this issue Feb 14, 2024 · 4 comments
Labels
to-evaluate Issue that has been received but not yet evaluated

Comments

@Guy2You
Copy link

Guy2You commented Feb 14, 2024

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

The current means (or at least the means that I used) of overriding the serializer on a single property of a class is convoluted.

Describe the solution you'd like

I would like a means of setting a custom serializer for a single property of a class where my custom serializer overrides any serializer applied to that property. Please delete this if it's a duplicate issue or if there is some way to do this that I missed.

Usage of a mixin for the class containing the property is inappropriate because I don't want to override the serialization of the class in any other way, just the property itself. Usage of SimpleModule.AddSerializer() for the property type is inappropriate because I don't want to override serialization of other properties of the same type.

Additionally I may only want to override the "regular" serializer and not the nullSerializer for the property.

I did find a way to accomplish this which I have provided an example of below, but it seems an easier way to accomplish this would be to remove the condition that blocks overriding the set serializer in a BeanPropertyWriter using the BeanPropertyWriter.assignSerializer(JsonSerializer<Object> ser) method, though I assume this is not that simple and that that condition is set for a reason.

Usage example

This is my workaround for the issue

// other json annotations here for this class
class MyClass {
  // this property should always be serialized using MyPropertySerializer
  @JsonSerialize(using = MyPropertySerializer.class)
  T myPropertyMySerialization = new MyProperty();

  // this property should be serialized using MyPropertySerializer when I don't want to override that behaviour below
  // BUT null values should be serialized with my NullSerializer
  @JsonSerialize(using = MyPropertySerializer.class, nullsUsing = NullSerializer.class)
  T myPropertyCustomSerialization = new MyProperty();
}

// elsewhere in the code
ObjectMapper myMethod() {
  SimpleModule module = new SimpleModule();
  module.setSerializerModifier(new BeanSerializerModifier() {

    @Override
    public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
      // modify serialization of MyClass
      if (MyClass.class.isAssignableFrom(beanDesc.getBeanClass())) {
        for (int i = 0; i < beanProperties.size(); i++) {
          BeanPropertyWriter beanPropertyWriter = beanProperties.get(i);
          // modify serialization of myPropertyCustomSerialization
          if ("myPropertyCustomSerialization".equals(beanPropertyWriter.getName()) {
            beanProperties.set(i, new MyCustomBeanPropertyWriter(beanPropertyWriter, new MyCustomPropertySerializer()));
          }
        }
        return beanProperties;
      }
      return super.changeProperties(config, beanDesc, beanProperties);
    }

    class MyCustomBeanPropertyWriter extends BeanPropertyWriter {
      private MyCustomBeanPropertyWriter(BeanPropertyWriter base, JsonSerializer<Object> newSerializer) {
        super(base);
        this._serializer = newSerializer;
      }
    }
  });
  JsonMapper.Builder builder = JsonMapper.builder();
  builder.addModule(module);
  return builder.build();
}

Additional context

Using version 2.15.3 of jackson-databind

@Guy2You Guy2You added the to-evaluate Issue that has been received but not yet evaluated label Feb 14, 2024
@cowtowncoder
Copy link
Member

I am not sure why explicit override on property that you showed is insufficient:

class MyClass {
  // this property should always be serialized using MyPropertySerializer
  @JsonSerialize(using = MyPropertySerializer.class)
  T myPropertyMySerialization = new MyProperty();
}

since that is the specific mechanism for overriding things on per-property basis.
It is also possible to use mix-ins to apply such annotation.
And there shouldn't be any need to use BeanSerializerModifier: annotation alone should work.

So I feel I am missing something here.

@Guy2You
Copy link
Author

Guy2You commented Feb 15, 2024

Rereading my question the following morning, you're right. My question was unclear and lacks important context, Sorry about that.

While Mixins would generally be an acceptable solution to this issue, in my case I'm not sure it is appropriate. I want to apply custom external configuration to the JsonMapper.Builder (above I'm doing this by adding my SimpleModule instance to the builder) to get the JsonMapper that it builds to serialize myPropertyCustomSerialization according to that configuration. I'm using this external configuration in the constructor of MyCustomPropertySerializer. I.e. on the line in the above code

beanProperties.set(i, new MyCustomBeanPropertyWriter(beanPropertyWriter, new MyCustomPropertySerializer()));

I am actually using the constructor new MyCustomPropertySerializer(configParameter). This serializer could look something like this.

class MyCustomPropertySerializer extends JsonSerializer<T> {
  private boolean serializeFirstProperty = true;
  
  // default no-arg constructor 
  public MyCustomPropertySerializer() {
    super();
  }
  
  // constructor to instantiate with my configuration
  public MyCustomPropertySerializer(boolean serializeFirstProperty) {
    this();
    this.serializeFirstProperty = serializeFirstProperty;
  }
  
  @Override
  public void serialize(T value, JsonGenerator gen, SerializerProvider serializers) {
    if (serializeFirstProperty)
    {
      gen.writePOJOField("prop1", value);
    }
    // other serialization omitted
  }
}

So I may construct different JsonMappers to serialize MyClass.myPropertyCustomSerialization in different ways according to what I want to do.

I hope this adds enough context and my question makes more sense now.

Thanks.

@cowtowncoder
Copy link
Member

Ok. Yes, that makes more sense; thank you for the explanation.

As things are, most similar configurability (dynamic, non-annotation based) is attached to types (classes); specifically "config overrides" allow changing various aspects (like serialization inclusion) but are attached to types.
There is currently no configurability mechanism that targets properties of POJOs, however. So that is something that would need to be added.
It could work either similar to Config Overrides (i.e. register class + property-name + serializer (etc) on ObjectMapper), or, probably more likely, add an extension point for Modules to register a callback that asks for serializer (etc) associated with class + property-name combo; called by BeanSerializerFactory (or wherever this was resolved).

In the meantime, I do think use of BeanSerializerModifier makes sense for this tho.

@Guy2You
Copy link
Author

Guy2You commented Feb 16, 2024

Thanks for your thoughts. I'll continue using the BeanSerializerModifier for this purpose in the way that I have done above. Personally I think that being able to register the serializer for the property on the Module makes the most sense to me. Creating an inner class to make use of BeanPropertyWriter's copy constructor and set the protected _serializer member feels like an awkward way to apply the wanted configuration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
to-evaluate Issue that has been received but not yet evaluated
Projects
None yet
Development

No branches or pull requests

2 participants