fracz
fracz

Reputation: 21249

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.

Upvotes: 4

Views: 6767

Answers (2)

wangyk
wangyk

Reputation: 53

thanks varren's answer, it works fine in springMvc's older version. but since 5.0, WebMvcConfigurerAdapter was deprecated. solution:

  1. may not work solution: we can implements WebMvcConfigurer directly for mvc config. but some config may not work, because WebMvcConfigurationSupport's priority is higher.
  2. suggest solution: we can extends WebMvcConfigurationSupport directly. imply configureMessageConverters method can add all kinds of custom HttpMessageConverters we need, and it can works fine before default converters.

spring framework is a amazing framework, I need to look it deeper after I got time.(●'◡'●)

Upvotes: 0

varren
varren

Reputation: 14731

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

Upvotes: 6

Related Questions