Spring data MongoDb: MappingMongoConverter remove _class

Viewed 31746

The default MappingMongoConverter adds a custom type key ("_class") to each object in the database. So, if I create a Person:

package my.dto;
public class Person {
    String name;
    public Person(String name) {
        this.name = name; 
    }
}

and save it to the db:

MongoOperations ops = new MongoTemplate(new Mongo(), "users");
ops.insert(new Person("Joe"));

the resulting object in the mongo will be:

{ "_id" : ObjectId("4e2ca049744e664eba9d1e11"), "_class" : "my.dto.Person", "name" : "Joe" }

Questions:

  1. What are the implications of moving the Person class into a different namespace?

  2. Is it possible not to pollute the object with the "_class" key; without writing a unique converter just for the Person class?

13 Answers

For Spring Boot 2.3.0.RELEASE it's more easy, just override the method mongoTemplate, it's already has all things you need to set type mapper. See the following example:

@Configuration
@EnableMongoRepositories(
// your package ...
)
public class MongoConfig extends AbstractMongoClientConfiguration {

    // .....

    @Override
    public MongoTemplate mongoTemplate(MongoDatabaseFactory databaseFactory, MappingMongoConverter converter) {
        // remove __class field from mongo
        converter.setTypeMapper(new DefaultMongoTypeMapper(null));
        return super.mongoTemplate(databaseFactory, converter);
    }

    // .....

}
@Configuration
public class MongoConfig {

    @Value("${spring.data.mongodb.database}")
    private String database;

    @Value("${spring.data.mongodb.host}")
    private String host;

    public @Bean MongoDbFactory mongoDbFactory() throws Exception {
        return new SimpleMongoDbFactory(new MongoClient(host), database);
    }

    public @Bean MongoTemplate mongoTemplate() throws Exception {

        MappingMongoConverter converter = new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory()),
                new MongoMappingContext());
        converter.setTypeMapper(new DefaultMongoTypeMapper(null));

        MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory(), converter);

        return mongoTemplate;

    }

}

The correct answer above seems to be using a number of deprecated dependencies. For example if you check the code, it mentions MongoDbFactory which is deprecated in the latest Spring release. If you happen to be using MongoDB with Spring-Data in 2020, this solution seems to be older. For instant results, check this snippet of code. Works 100%. Just Create a new AppConfig.java file and paste this block of code. You'll see the "_class" property disappearing from the MongoDB document.

package "Your Package Name";

import org.apache.naming.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;  
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;

@Configuration
public class AppConfig {

@Autowired
MongoDatabaseFactory mongoDbFactory;
@Autowired
MongoMappingContext mongoMappingContext;

@Bean
public MappingMongoConverter mappingMongoConverter() {

    DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory);
    MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
    converter.setTypeMapper(new DefaultMongoTypeMapper(null));

    return converter;
    }

}

I'm using:

package YOUR_PACKAGE;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;

@Configuration
public class MongoConfiguration {


  @Autowired
  private MappingMongoConverter mongoConverter;

  @PostConstruct
  public void setUpMongoEscapeCharacterAndTypeMapperConversion() {
      mongoConverter.setMapKeyDotReplacement("_");
      
      // This will remove _class: key
      mongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
  }

}

Btw: It is also replacing "." with "_"

you just need to add the @TypeAlias annotation to the class defintion over changing the type mapper

I've tried the solutions above, some of them don't work in combination with auditing, and none seems to set correctly the MongoCustomConversions

A solution that works for me is the following

@Configuration
public class MongoConfig {

    @Bean
    public MappingMongoConverter mappingMongoConverterWithCustomTypeMapper(
            MongoDatabaseFactory factory,
            MongoMappingContext context,
            MongoCustomConversions conversions) {
        DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
        MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, context);
        mappingConverter.setCustomConversions(conversions);

        /**
         * replicate the way that Spring
         * instantiates a {@link DefaultMongoTypeMapper}
         * in {@link MappingMongoConverter#MappingMongoConverter(DbRefResolver, MappingContext)}
         */
        CustomMongoTypeMapper customTypeMapper = new CustomMongoTypeMapper(
                context,
                mappingConverter::getWriteTarget);
        mappingConverter.setTypeMapper(customTypeMapper);
        return mappingConverter;
    }
}

public class CustomMongoTypeMapper extends DefaultMongoTypeMapper {

    public CustomMongoTypeMapper(
            MappingContext<? extends PersistentEntity<?, ?>, ?> mappingContext,
            UnaryOperator<Class<?>> writeTarget) {
        super(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext, writeTarget);
    } 

    @Override
    public TypeInformation<?> readType(Bson source) {

    /**
     * do your conversion here, and eventually return
     */
    return super.readType(source);
    }
}

As an alternative, you could use a BeanPostProcessor to detect the creation of a mappingMongoConverter, and add your converter there.

Something like

public class MappingMongoConverterHook implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if ("mappingMongoConverter" == beanName) {
            ((MappingMongoConverter) bean).setTypeMapper(new CustomMongoTypeMapper());
        }
        return bean;
    }
}
Related