Spring @ConfigurationProperties instance to JSON with jackson: No serializer found

时间:2019-04-08 12:51:28

标签: json mongodb spring-boot java-8 jackson

I'm having the following code:

@Data
@Validated
@ConfigurationProperties
public class Keys {

    private final Key key = new Key();

    @Data
    @Validated
    @ConfigurationProperties(prefix = "key")
    public class Key {

        private final Client        client          = new Client();
        private final IntentToken   intentToken     = new IntentToken();
        private final Intent        intent          = new Intent();
        private final OAuth         oauth           = new OAuth();
        private final ResourceToken resourceToken   = new ResourceToken();

        @Valid @NotNull private String authorization;
        @Valid @NotNull private String bearer;
    ...
    }
}

That is an instance representing a properties file such as:

key.authorization=Authorization
key.bearer=Bearer
..

As I can have different sources for the properties (properties file, MongoDB, etc), I have a client that inherit from Keys as follow:

Properties files source

@Component
@Configuration
@Primary
@PropertySource("classpath:${product}-keys.${env}.properties")
//@JsonAutoDetect(fieldVisibility = Visibility.ANY)
public class CustomerKeysProperties extends Keys {

}

Mongo source

@Data
@EqualsAndHashCode(callSuper=true)
@Component
//@Primary
@Document(collection = "customerKeys")
public class CustomerKeysMongo extends Keys {
    @Id
    private String id;
}

I just select the source I want to use annotating the class with @Primary. In the example above, CustomerKeysProperties is the active source.

All this work fine. The issue I have is when I try to convert an instance of CustomerKeysProperties into JSON, as in the code below:

@SpringBootApplication
public class ConverterUtil {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(ConverterUtil.class, args);
    }

    @Component
    class CustomerInitializer implements CommandLineRunner {

        @Autowired 
        private Keys k;
        private final ObjectMapper mapper = new ObjectMapper();

        @Override
        public void run(String... args) throws Exception {
            mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
            //mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
            String jsonInString = mapper.writeValueAsString(k);
            System.out.println(jsonInString);
        }
    }
}

While k contains all the properties set, the conversion fails:

Caused by:     com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: x.client.customer.properties.CustomerKeysProperties$$EnhancerBySpringCGLIB$$eda308bd["CGLIB$CALLBACK_0"]->org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor["advised"]->org.springframework.aop.framework.ProxyFactory["targetSource"]->org.springframework.aop.target.SingletonTargetSource["target"]->x.client.customer.properties.CustomerKeysProperties$$EnhancerBySpringCGLIB$$4fd6c568["CGLIB$CALLBACK_0"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1191)

And if I uncomment mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) as suggested in the logs, I have an infinite loop happening in Jackson causing a stackoverflow:

at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:79)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:18)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
..

Questions

At the end, I just want to provide an Util class than can convert a properties file in a JSON format that will be stored in MongoDB.

  1. How can I solve this problem ?
  2. Without passing through the object above, how can I transform a properties file into JSON ?
  3. Can I save an arbitrary Java bean in MongoDB, with the conversion to JSON automagically done ?

The answer to any of the 3 questions above would be helpful.

Notes

  • To be noted that I use lombok. Not sure if this is the problem.
  • Another guess is that I'm trying to serialize a Spring managed bean and the proxy it involve cause jackson to not be able to do the serialization ? If so, what can be the turn-around ?

Thanks!

1 个答案:

答案 0 :(得分:0)

因此发现了问题:

jackson无法处理托管bean。

转身是

            try (InputStream input = getClass().getClassLoader().getResourceAsStream("foo.properties")) {
                JavaPropsMapper mapper = new JavaPropsMapper();
                Keys keys = mapper.readValue(input, Keys.class);
                ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
                String res = ow.writeValueAsString(keys);
                System.out.println(res);
            } catch (IOException e) {
                e.printStackTrace();
            }

其中Keys是我正在注入的Spring托管bean。

并且:

JavaPropsMapper来自:

    <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-properties</artifactId>
    </dependency>