当前位置: 首页 > 知识库问答 >
问题:

使用Jackson和Spring Boot的条件JsonProperty

葛智敏
2023-03-14

Spring Boot应用程序的任务是每隔这么多分钟更新一次远程集成API。此应用程序可以部署到测试或prod环境中,通过“application.properties”标志通知应用程序应该查看的endpoint。POJO被Jackson序列化并推送到endpoint,JsonProperty注释包含它被推送到的API的字段ID。

ie

@JsonProperty("field_001)
private String name;

@JsonProperty("field_002)
private String address;

这些值的字段标签在测试endpoint上不同。因此,测试endpoint可能期望属性映射为

@JsonProperty("field_005)
private String name;

@JsonProperty("field_006)
private String address;

我希望能够利用Spring Boot对基于概要文件的属性文件的本机支持。在运行时从外部属性文件中读取JsonProperty注释值。

比如说,

应用程序中可能有三个文件。属性,应用测试。属性和application-prod.properties。Spring Boot可以根据“Spring.profiles.active”设置读取除vanilla属性文件之外的测试或产品属性。

...-测验属性将包含测试服务器字段的常量值。而且-properties将包含prod server字段的常量值。

嵌套注释,如Spring的@Value标记,如下所示:

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

似乎不管用。

共有3个答案

卢枫涟
2023-03-14

这是马蒂斯的补充答案。

解决方案使用扩展的JacksonAnNotationINBERCOTOR,允许在@JsonProperty注释中使用${environment.properties}

public class DynamicJacksonAnnotationIntrospector extends JacksonAnnotationIntrospector {
    private final Environment environment;

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

    @Override
    public PropertyName findNameForSerialization(Annotated a) {
        return injectEnvironmentInJsonProperty(super.findNameForSerialization(a));
    }

    @Override
    public PropertyName findNameForDeserialization(Annotated a){
        return injectEnvironmentInJsonProperty(super.findNameForDeserialization(a));
    }

    @Override
    public PropertyName findRootName(AnnotatedClass ac) {
        return injectEnvironmentInJsonProperty(super.findNameForDeserialization(ac));
    }

    private PropertyName injectEnvironmentInJsonProperty(PropertyName name){
        if (name == null) {
            return null;
        }
        String simpleName = name.getSimpleName();
        log.info(environment.resolvePlaceholders(simpleName));
        return PropertyName.construct(environment.resolvePlaceholders(simpleName), name.getNamespace());
    }
}

为控制器中的实体识别创建webconfig类。

@EnableWebMvc
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {

    private final ApplicationContext context;

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        //JSON
        AnnotationIntrospector pairedIntrospectors = AnnotationIntrospector.pair(introspector(context.getEnvironment()),
                new JacksonAnnotationIntrospector());
        converters.add(new MappingJackson2HttpMessageConverter(
                Jackson2ObjectMapperBuilder.json()
                        .annotationIntrospector(pairedIntrospectors)
                        .build()));
    }

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

    @Bean
    @Primary
    public ObjectMapper getObjectMapper(DynamicJacksonAnnotationIntrospector introspector) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        SerializationConfig serializationConfig = mapper.getSerializationConfig()
                .withInsertedAnnotationIntrospector(introspector);
        mapper.setConfig(serializationConfig);
        DeserializationConfig deserializationConfig = mapper.getDeserializationConfig()
                .withInsertedAnnotationIntrospector(introspector);
        mapper.setConfig(deserializationConfig);
        return mapper;
    }
}

并在spring boot的自动配置中禁用类

spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

例子

@Getter
@Setter
public class DynamicTestClass {
    @JsonProperty("${dynamic.property.name}")
    private String dynamicPropertyName;
}
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK, classes = JiraSmCoreApplication.class)
@TestPropertySource("classpath:application-test.yml")
public class DynamicJacksonAnnotationIntrospectorTest {
    @Autowired
    MappingJackson2HttpMessageConverter mapper;

    @Test
    public void shouldFindNameForSerializationFromProperties() throws JsonProcessingException {
        DynamicTestClass bean = new DynamicTestClass();
        bean.setDynamicPropertyName("qwerty");
        log.info(mapper.getObjectMapper().writeValueAsString(bean));
    }
}

应用测试。yml

spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

dynamic:
  property:
    name: overriddenName
曾元忠
2023-03-14

我怀疑您能否在Jackson注释中使用Spring Expression Language(SpEL)实现这一点,因为您正在尝试(使用或不使用@Value注释)。

我将通过创建一个JsonSerializer来实现这一点

//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();
    }
}

由于这是一个Spring管理的bean,所以需要将其插入Spring管理的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);

SpringBoot的自动配置可能不需要这样做。我通常不知道SpringBoot将为其Jackson自动配置选择什么,但是如果JsonSerializerJsonDeserializer位于ApplicationContext中,它们可能会自动注册。

竺捷
2023-03-14

很抱歉,我又提了一个老问题,但我仍然找不到满意的答案。

下面是我使用扩展的JacksonAnnotationIntrospector的解决方案,它允许在@JsonProperty注释中使用${environment.properties}

首先扩展内省者

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
}

然后使用它与ObjectMapper配置

@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);
    }
}

例子:

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\"}"
    }
}

测验属性

dynamic.property.name=overriddenName

该解决方案是反向兼容的,因此您仍然可以在@JsonProperty

 类似资料:
  • 我正在使用与类似 我有在我的主应用程序类上 但是,不起作用。如果我的属性是false,并且我注释掉yaml文件中的CloudBucket对象,它在启动时失败,因为它不能绑定云桶属性。如果属性是false,那么该对象不应该是必需的,然后bean应该是null。我如何使这个工作?

  • 我想启用或禁用具有外部配置的SSL/TLS,这些配置可以在应用程序启动期间提供。应用程序应该支持http和HTTPS的所有crud操作。 既然上面的属性是不推荐使用的,那么我如何在不使用配置文件的情况下实现它。

  • 我可以有条件地使用@JsonUnwrapped吗?我不想在序列化期间使用它,但希望在反序列化对象时使用它。 一种方法是创建两个不同的类,或者创建一个子类覆盖那个属性,这个属性在序列化和反序列化时需要表现得不同。这听起来不太对。还有其他替代方法吗?或者杰克逊解决这个问题的方法吗?

  • 我试图上传多个文件使用Spring mvc 4,Spring引导和thymeleaf作为模板引擎,但我无法访问上传的文件,文件被处理为一个多部分文件与内容类型的应用程序/octet-stream. 以及控制器代码: sysout的输出: 上传图像长度:1(即使我上传了多个文件) 文件原始名称(使用getOrialFileName): 文件名(使用getName):文件[] 文件大小: 0 文件内容

  • 问题内容: 使用样本数据: df 我试图弄清楚如何按key1分组数据并仅对key2等于“ one”的data1值求和。 这是我尝试过的 但这给了我一个数值为“无”的数据框 这里有什么想法吗?我正在寻找与以下SQL等效的Pandas: 提前致谢 问题答案: 首先按key1列分组: 然后为每个组取subDataFrame,其中key2等于“ one”并求和data1列: 为了解释发生了什么,让我们看一

  • 问题内容: 希望您能帮到我。我有一个DF,如下所示: 我很乐意做一个groupBy prodId并汇总“值”,以将其汇总为由“ dateIns”和“ dateTrans”列之间的差异所定义的日期范围。特别是,我希望有一种方法来定义一个条件总和,该总和将上述各列之间的预定义最大差之内的所有值相加。即从dateIns开始的10、20、30天之间发生的所有值(’dateTrans’-‘dateIns’<