Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deserializing enums as interfaces with Spring Data MongoDB [DATAMONGO-1884] #2786

Open
spring-projects-issues opened this issue Feb 22, 2018 · 2 comments
Assignees
Labels
in: mapping Mapping and conversion infrastructure type: bug A general bug

Comments

@spring-projects-issues
Copy link

Joaquin Peñalver opened DATAMONGO-1884 and commented

I've currently a problem with serialize/deserialize process in MongoDB of an object that contains an attribute defined by a marker interface and the implementations of this interface are Enums.

My used versions are Spring Boot 1.5.2, Spring 4.3.7 and Spring-data-mongodb 1.10.1.

The piece of code afected is:

public interface EventType {
    String getName(); 
}
public interface DomainEvent extends Serializable {

    UUID getId();    
    LocalDateTime getOccurredOn();    
    EventType getEventType();    
    String getEventName();    
}
public abstract class AbstractDomainEvent implements DomainEvent {

    private UUID id;
    private LocalDateTime occurredOn;
    private EventType eventType;

    protected AbstractDomainEvent(EventType eventType) {
        this.id = UUID.randomUUID();
        this.occurredOn = LocalDateTime.now();
        this.eventType = eventType;
    }
}
public class MyEventOne extends AbstractDomainEvent {

    private Object myConcreteData;

    public MyEventOne(Object data) {

        super(MyEventType.EVENT_ONE);
        this.myConcreteData = data;
    }    
}
public enum MyEventType implements EventType {

    EVENT_ONE,
    EVENT_N;

    @Override
    public String getName() {
        return this.name();
    }    
}

Ok, well. My problem is when I try to deserialize an event persisted in mongoDB.

When I persist MyEventOne, Spring data mongo persist the object as:

{
    "_class" : "xxx.xxx.xxx.MyEventOne",
    "_id" : LUUID("d74478e7-258c-52c4-4fc5-aba20a30d4b6"),
    "occurredOn" : ISODate("2018-02-21T14:39:53.549Z"),
    "eventType" : "EVENT_ONE"
}

Note the eventType is serialized as String, I know that String is the default representation of a Enum (real class), but in this case is referenced as a interface EventType in AbstractDomainEvent.

When I try to read this document, I have this exception:

org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [xxx.xxx.xxx.EventType]

Any idea? May be it's a bug? May be spring-data-mongo should resolve this situation inserting a metadata information about the concrete Enum instance, like a "_class" field?

I try insert @JsonTypeInfo annotation in EventType attribute at AbstractDomainEvent but it doesnt works, i think that it's ignored.

The temporary solution might be write a custom converter, but i think that this feature can be a framework feature.

Thanks in advcance!


Reference URL: https://stackoverflow.com/questions/48910761/deserializing-interfaces-with-spring-data-mongodb

@spring-projects-issues
Copy link
Author

Oliver Drotbohm commented

It is a bug indeed but a tricky one to solve. We currently write enums as their names as thats the most efficient way to store them. However you should be able to register a Converter<MyEventType, DBObject> to make sure the enum values get written as nested documents and a Converter<DBObject, EventType> to then read the written structure again and produce enum values based on the type information you wrote.

It's a bit hard to suggest a perfect way to solve that as your setup indicates that there could be other implementations of EventType as well and especially on the reading side you might already need to inspect the source value to decide which of the converters to actually use for reading. Because of those specifics, I'm not quite sure we can / should be actually change our default behavior

@spring-projects-issues spring-projects-issues added type: bug A general bug in: mapping Mapping and conversion infrastructure labels Dec 30, 2020
@davidnewcomb
Copy link

I have hit this problem too.

Enum

public enum MyEnum {

	TITLE("title");

	private String value = "";

	private static final Map<String, MyEnum> BY_VALUE = new HashMap<>();

	static {
		for (MyEnum e : values()) {
			BY_VALUE.put(e.value, e);
		}
	}

	private MyEnum(String value) {
		this.value = value;
	}

	@Override
	public String toString() {
		return value;
	}

	public static MyEnum byValue(String val) {
		return BY_VALUE.get(val);
	}
}

Domain class

@Data
@Document(collection = "stuff")
public class MongoStuff {
	private Map<MyEnum, String> props = new HashMap<>();
}

Repository

public interface MyStuffRepository extends MongoRepository<MongoStuff, ObjectId> {}

Code - write

MongoStuff ms = new MongoStuff();
ms.getProps().put(MyEnum.TITLE, "Arrrrgghhh!");
myStuffRepository.save(ms);

Creates:

{ _id: ObjectId(1234...), "props": { "title": "Arrrrgghhh!" }}

Code - read

ms = myStuffRepository.findById(ObjectId(1234..)).orElseThrow(....);

but it blows up with message in reporter's comment.

It seems you are using Enum.toString() to write and not Enum.name() (which it probably should do for conversion consistancy) because the toString value is in the db as the Map key and not the name() value of "TITLE".

I have added Converter<MyEnum, String> and Converter<String, MyEnum> and tried with each one separately and both together and it either prevents queries on other collections (fields become null when they get to the db level) or it defaults to the Standard Enum converter which blows up.

To me it looked like it was mostly working because of the toString/name inconsistancy but then the Converters didn't work either I had to start Googling and here I am.

I have other Enums in my domain classes and that all works fine but having the Enum be part of a Map doesn't work.

The default implementation 50% works, prehaps you could inject a byValue and get the other 50% :) or better still get the converters to honour my enum types instead of the base Enum type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: mapping Mapping and conversion infrastructure type: bug A general bug
Projects
None yet
Development

No branches or pull requests

3 participants