1

How to properly connect jackson to a standalone SpringBoot application without unnecessary dependencies (web)?

Java 18

plugins {
    id 'org.springframework.boot' version '2.7.5-SNAPSHOT'
    id 'io.spring.dependency-management' version '1.0.13.RELEASE'
    id 'java'
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter'
  implementation 'org.springframework.boot:spring-boot-starter-json:2.7.5-SNAPSHOT'
  compileOnly 'org.projectlombok:lombok'
  developmentOnly 'org.springframework.boot:spring-boot-devtools'
  annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
  annotationProcessor 'org.projectlombok:lombok'
  testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

External Libraries in IDEA

  • Does this answer your question? [gradle exclude a transitive dependency](https://stackoverflow.com/questions/62868920/gradle-exclude-a-transitive-dependency) – Cisco Sep 28 '22 at 18:35

2 Answers2

4

Spring Boot's auto-configuration for Jackson requires spring-web as it's the Spring Framework module that contains the infrastructure for configuring and creating a Jackson ObjectMapper. If you really don't want a spring-web dependency in your application, you could replace the dependency on spring-boot-starter-json with a direct dependency on Jackson instead, however you will then have to configure and create the ObjectMapper yourself as Spring Boot will no longer do it for you.

Andy Wilkinson
  • 108,729
  • 24
  • 257
  • 242
1

I created a custom jackson standalone configuration.

It only contains what I need, but you could easily copy the bits in the JacksonAutoConfiguration for the jacksonProperties or anything else that you need and add them here. And then remove the JacksonAutoConfiguration class and use the code snippet below.

I just wanted to keep it simple because mine was for a redis synchronization service.

The optimal solution would be to rewrite the code in JacksonAutoConfiguration and drop the Jackson2ObjectMapper and use JsonMapper.builder() instead and then they could drop the spring-web dependency entirely.

@Configuration
public class JacksonStandaloneConfiguration {

    @Bean
    public ObjectMapper jacksonOjectMapper(ObjectProvider<Module> provider) {
        // All modules declared as beans anywhere else
        List<Module> modulesToInstall = provider.stream().toList();
        Builder builder = JsonMapper.builder();
        ObjectMapper mapper = configure(builder);
        registerModules(mapper, modulesToInstall);
        return mapper;
    }

    private ObjectMapper configure(Builder builder) {
        return builder //
                // Spring boot defaults
                .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) //
                .disable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS) //
                // Jackson2ObjectMapperBuilder defaults
                .disable(MapperFeature.DEFAULT_VIEW_INCLUSION) //
                .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) //
                .build();
    }

    private static void registerModules(ObjectMapper mapper, List<Module> modulesToInstall) {
        MultiValueMap<Object, Module> modulesToRegister = new LinkedMultiValueMap<>();
        registerWellKnownModulesIfAvailable(modulesToRegister);
        modulesToInstall.forEach(module -> registerModule(module, modulesToRegister));
        List<Module> modules = new ArrayList<>();
        for (List<Module> nestedModules : modulesToRegister.values()) {
            modules.addAll(nestedModules);
        }
        mapper.registerModules(modules);
    }

    private static void registerWellKnownModulesIfAvailable(MultiValueMap<Object, Module> modulesToRegister) {
        // Jackson2ObjectMapperBuilder defaults (You could add these as beans and remove this method entirely)
        registerModule(new Jdk8Module(), modulesToRegister);
        registerModule(new JavaTimeModule(), modulesToRegister);
//      registerModule(new JodaModule(), modulesToRegister);
//      registerModule(new KotlinModule(), modulesToRegister);
    }

    private static void registerModule(Module module, MultiValueMap<Object, Module> modulesToRegister) {
        if (module.getTypeId() == null) {
            modulesToRegister.add(SimpleModule.class.getName(), module);
        } else {
            modulesToRegister.set(module.getTypeId(), module);
        }
    }

}
flyingfishcattle
  • 1,817
  • 3
  • 14
  • 25
Triqui
  • 11
  • 1