0

Document looks like:

@Document(collection="Contact")
public class Contact {

    @Id
    private String id;

    private Map<String, String> properties;
}

The repository is like:

@Repository
public interface ContactRepository extends MongoRepository<Contact, String> {
}

This will save data in database like this:

_id: "5f16699eb57ab928fdf0e893",
properties:
{
  customField: "customValue"
}

But I want result like this:

_id: "5f16699eb57ab928fdf0e893",
customField: "customValue"
ikos23
  • 4,879
  • 10
  • 41
  • 60
  • Sadly, it seems there is no known answer... Others similar posts are without answers, like : https://stackoverflow.com/questions/54950724/how-to-flatten-dynamic-field-with-parent-document-spring-data-mongo-db. – RUARO Thibault Jul 28 '20 at 10:09

1 Answers1

0

I've made my research, and you can do it by implementing your own Custom Converters (as specified here: https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mongo.custom-converters)

The solution is:

  1. Create your specific converters, one for the reader, the other for the writer
import org.bson.Document;
import org.springframework.core.convert.converter.Converter;

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

public class ContactReadConverter implements Converter<Document, Contact> {

    @Override
    public Contact convert(Document source) {
        Contact c = new Contact();
        c.setId((Long) source.get("_id"));
        Map<String, String> props = new HashMap<>();
        source.keySet().stream()
                .filter(key -> !key.equals("_id"))
                .forEach(key -> props.put(key, (String) source.get(key)));
        c.setProperties(props);
        return c;
    }
}
import org.bson.Document;
import org.springframework.core.convert.converter.Converter;

public class ContactWriteConverter implements Converter<Contact, Document> {

    @Override
    public Document convert(Contact source) {
        Document dbo = new Document();
        dbo.put("_id", source.getId());
        source.getProperties()
                .forEach(dbo::put);
        return dbo;
    }
}
  1. Then, you need to add those converters into MongoDB. Here is how I did:
@SpringBootApplication
public class MainClass {
   ...

   @Bean
    public MongoCustomConversions customConversions() {
        List<Converter<?, ?>> converterList = new ArrayList<>();
        converterList.add(new ContactReadConverter());
        converterList.add(new ContactWriteConverter());
        return new MongoCustomConversions(converterList);
    }
}

And my Contact class, just in case:

import org.springframework.data.mongodb.core.mapping.Document;

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.util.Map;

@Document
public class Contact {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private Map<String, String> properties;

    public Contact() {
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public Map<String, String> getProperties() {
        return properties;
    }

    public void setProperties(Map<String, String> properties) {
        this.properties = properties;
    }

    @Override
    public String toString() {
        return "Contact{" +
                "id=" + id +
                ", properties=" + properties +
                '}';
    }
}

And my result is:

{ "_id" : NumberLong(0), "toto" : "titi", "tata" : "tonton" }

Things to consider:

  • Customize for your need. Because with the solutions provided, you will interpret every other field you might have and store it to your Map. Which is not so great...
  • Be careful, I hope there is no impact on the "basic" mongo configuration with default converter.
RUARO Thibault
  • 2,672
  • 1
  • 9
  • 14