Skip to content

How to serialize nested ObjectId to String with Jackson?

There are many questions concerning conversion from ObjectId to String with jackson. All answers suggest either creating own JsonSerializer<ObjectId> or annotating the ObjectId field with @JsonSerialize(using = ToStringSerializer.class).

However, I have a map that sometimes contains ObjectIds, i.e.:

class Whatever {
  private Map<String, Object> parameters = new HashMap<>();
  Whatever() {
    parameters.put("tom", "Cat");
    parameters.put("jerry", new ObjectId());
  }
}

I want jackson to convert it to:

{
  "parameters": {
    "tom": "cat",
    "jerry": "57076a6ed1c5d61930a238c5"
  }
}

But I get:

{
  "parameters": {
    "tom": "cat",
    "jerry": {
      "date": 1460103790000,
      "machineIdentifier": 13747670,
      "processIdentifier": 6448,
      "counter": 10631365,
      "time": 1460103790000,
      "timestamp": 1460103790,
      "timeSecond": 1460103790
    }
  }
}

I have registered the conversion (in Spring) with

public class WebappConfig extends WebMvcConfigurerAdapter {
  @Override
  public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
    builder
        .serializerByType(ObjectId.class, new ToStringSerializer());
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(builder.build());
    converters.add(converter);
  }
}

And the first-level ObjectIds are converted correctly. How to make jackson convert also the nested ones? Do I have to write custom converter for this map?

Keep in mind that this Map can be nested multiple times (i.e. contain another maps). I just want to convert ObjectId to String whenever jackson sees it.

Answer

I suppose that you are taking about org.bson.types.ObjectId from org.springframework.boot:spring-boot-starter-data-mongodb. Your code works perfectly fine for me. 1 thing i can see is that you don’t show @Configuration annotation above WebappConfig.

Here is my demo project, can you test it on yours setup?

Application.java

import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.bson.types.ObjectId;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
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.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@SpringBootApplication
public class Application {

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

    @Configuration
    public static class WebappConfig extends WebMvcConfigurerAdapter {
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
            builder
                    .serializerByType(ObjectId.class, new ToStringSerializer());
            MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(builder.build());
            converters.add(converter);
        }
    }

    @RestController
    public static class MyRestController {

        @ResponseBody
        @RequestMapping("/")
        public Whatever method() {
            return new Whatever();
        }
    }

    public static class Whatever {
        private Map<String, Object> parameters = new HashMap<>();

        public Whatever() {
            parameters.put("tom", "Cat");
            parameters.put("jerry", new ObjectId());
        }

        public Map<String, Object> getParameters() {
            return parameters;
        }

        public void setParameters(Map<String, Object> parameters) {
            this.parameters = parameters;
        }
    }
}

Responce from 127.0.0.1:8080

{
  "parameters": {
    "tom": "Cat",
    "jerry": "5709df1cf0d9550b4de619d2"
  }
}

Gradle:

dependencies {
    compile("org.springframework.boot:spring-boot-starter-data-mongodb")
    compile("org.springframework.boot:spring-boot-starter-web")
}