Conditional JsonProperty using Jackson with Spring Boot

杀马特。学长 韩版系。学妹 提交于 2021-02-10 23:35:59

问题


A Spring Boot application is tasked with updating a remote integration API every so many minutes. This application can be deployed to a test or prod environment, the application is informed of the end point it should be looking at through an "application.properties" flag. A POJO is being serialized with Jackson and pushed to the endpoint, with the JsonProperty annotations containing the field IDs for the API that it is being pushed to.

ie

@JsonProperty("field_001)
private String name;

@JsonProperty("field_002)
private String address;

The field labels for these values differ on the test endpoint. So the test endpoint might expect the properties to map as

@JsonProperty("field_005)
private String name;

@JsonProperty("field_006)
private String address;

I would like to be able to utilize the Spring Boot native support for profile based properties files. To read in the JsonProperty annotation values at run time from an external properties file.

So for example,

There might be three files application.properties, application-test.properties and application-prod.properties. Spring Boot could read in the test or prod properties in addition to the vanilla properties file based on the "spring.profiles.active" setting.

...-test.properties would contain the constant values for the test server fields. And ...-prod.properties would contain the constant values for the prod server fields.

Nesting annotations such as Spring's @Value tag, like this:

@JsonProperty(@Value("${property.file.reference.here})) 

doesn't seem to work.


回答1:


I apologize for reviving an old question however I still was not able to find satisfying answer.

Here's my solution using extended JacksonAnnotationIntrospector which allows to use ${environment.properties} within @JsonProperty annotation

First extend the introspector

public class DynamicJacksonAnnotationIntrospector extends JacksonAnnotationIntrospector {
    private final Environment environment;

    public DynamicJacksonAnnotationIntrospector(Environment environment) {
        this.environment = environment;
    }

    @Override
    public PropertyName findNameForSerialization(Annotated a) {
        PropertyName name = super.findNameForSerialization(a);
        if (name == null) {
            return null;
        }
        String simpleName = name.getSimpleName();
        return PropertyName.construct(environment.resolvePlaceholders(simpleName), name.getNamespace());
    }
    //For deserialization I think the same mechanism could be used,
    //just override `findNameForDeserialization`, although I haven't tested it
}

Then use it with ObjectMapper configuration

@Configuration
public class ObjectMapperConfiguration {
    @Bean
    public ObjectMapper getObjectMapper(DynamicJacksonAnnotationIntrospector introspector) {
        ObjectMapper mapper = new ObjectMapper();
        SerializationConfig config = mapper.getSerializationConfig().withInsertedAnnotationIntrospector(introspector);
        mapper.setConfig(config);
        return mapper;
    }

    @Bean
    public DynamicJacksonAnnotationIntrospector introspector(Environment environment) {
        return new DynamicJacksonAnnotationIntrospector(environment);
    }
}

Examples:

public class DynamicTestClass {
    @JsonProperty("${dynamic.property.name}")
    private String dynamicPropertyName;
    //getters/setters
}
@ContextConfiguration(classes = [
        ObjectMapperConfiguration
])
@TestPropertySource("classpath:test.properties")
class DynamicJacksonAnnotationIntrospectorTest extends Specification {
    @Autowired
    ObjectMapper mapper

    def "should find name for serialization from properties"() {
        def bean = new DynamicTestClass()
        bean.dynamicPropertyName = "qwerty"

        when:
        def result = mapper.writeValueAsString(bean)

        then:
        result == "{\"overriddenName\":\"qwerty\"}"
    }
}

test.properties

dynamic.property.name=overriddenName

The solution is reverse compatible so you can still use constant values in @JsonProperty




回答2:


I doubt you will be able to do this using Spring Expression Language (SpEL) inside of a Jackson annotation, as you are trying (with or without the @Value annotation).

I would do this by creating a JsonSerializer<YourPojo> and/or JsonDeserializer<YourPojo> that takes in your SpEL expressions and creates (or reads) using the provided field names.

//make me a spring managed bean!
public class PojoSerializer extends JsonSerializer<YourPojo> {
    @Value("${property.file.reference.name")
    private String nameField;

    @Value("${property.file.reference.address")
    private String addrField;

    @Override
    public void serialize(YourPojo pojo, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
        jgen.writeStartObject();
        jgen.writeStringField(nameField, pojo.getName());
        jgen.writeStringField(addrField, pojo.getAddress());
        jgen.writeEndObject();
    }
}

Being that this is a Spring managed bean, you would need to plug this into your Spring managed ObjectMapper.

ObjectMapper mapper = //my ObjectMapper from spring
PojoSerializer pojoSerializer = //my PojoSerializer from spring

SimpleModule module = new SimpleModule("MyModule", new Version(1, 0, 0, null));
module.addSerializer(YourPojo.class, pojoSerializer);
mapper.registerModule(module);

Some of this might not be necessary with SpringBoot's AutoConfiguration. I am generally unaware to what SpringBoot will pick up for its Jackson AutoConfiguration, but JsonSerializer and JsonDeserializer might be autoregistered if they are in the ApplicationContext.



来源:https://stackoverflow.com/questions/35089257/conditional-jsonproperty-using-jackson-with-spring-boot

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!