2

We are trying to externalize a date format in a bean for a field using the @JSONFormat so as to make it configurable.

@JsonFormat(pattern = "${application.date.format}")
private Date creationtime;

OR

@JsonFormat(pattern = "yyyy-MM-DD")
private Date creationtime;

It works when I give a standard String value. However when we externalize the value as shown in the first one, I am getting an exception saying :

java.lang.IllegalArgumentException: Illegal pattern character 'p'

to assign a variable value to the second approach we need a final String which is a constant expression. How can I make the pattern configurable?

stackMan10
  • 732
  • 6
  • 25
  • That won't work as Spring doesn't know `@JsonFormat` so it won't replace the value. This will only work for `@Value` or Spring based annotations with explicit support for this. – M. Deinum Dec 02 '20 at 07:07
  • @M. Deinum Is there any other way to achieve this? The annotation attribute value needs a constant expression so I can't use a property with **@Value** as well. – stackMan10 Dec 02 '20 at 07:28
  • 1
    Not with an annotation. You would need to globally configure jackson or use a custom serializer which does this on the fly. – M. Deinum Dec 02 '20 at 07:54

1 Answers1

1

Jackson uses JacksonAnnotationIntrospector to handle standard annotations. We can extend this mechanism providing our extra introspector together with default. To do that we can use pair method.

Simple custom implementation could look like below:

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.introspect.Annotated;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

import java.util.Objects;

public class SpringJacksonAnnotationIntrospector extends AnnotationIntrospector {

    private final ApplicationContext context;

    @Autowired
    public SpringJacksonAnnotationIntrospector(ApplicationContext context) {
        this.context = Objects.requireNonNull(context);
    }

    @Override
    public JsonFormat.Value findFormat(Annotated memberOrClass) {
        JsonFormat annotation = _findAnnotation(memberOrClass, JsonFormat.class);
        if (annotation == null) {
            return null;
        }
        String basePattern = annotation.pattern();
        if (basePattern.startsWith("$")) {
            String pattern = context.getEnvironment().getProperty(basePattern.substring(2, basePattern.length() - 1));
            return JsonFormat.Value.forPattern(pattern);
        }
        return null;
    }

    @Override
    public Version version() {
        return new Version(1, 0, 0, "", "org.company", "spring-jackson");
    }
}

Above implementation reuses JsonFormat annotation but we can introduce new one to avoid confusion. We need to register our custom introspector. There is many different ways how to do that and it depends from your Spring configuration. We can do that for example, like below:

import com.example.jackson.SpringJacksonAnnotationIntrospector;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@EnableWebMvc
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private ApplicationContext context;

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

    @Bean
    public SpringJacksonAnnotationIntrospector springJacksonAnnotationIntrospector() {
        return new SpringJacksonAnnotationIntrospector(context);
    }
}

Since now, Spring's global configuration should be used to override date format.

See also:

Michał Ziober
  • 37,175
  • 18
  • 99
  • 146