Map<String, Object> does not serialize propperly

1,445 views
Skip to first unread message

azlists

unread,
Sep 11, 2014, 6:40:21 AM9/11/14
to mor...@googlegroups.com
Hi 

I'm trying to save an object that contains a Map<String,Object>. 
The values of the Map can be of different types.
Serialization does not work when the values in the Map are complex Objects (if I remember correctly simple types work properly). 
How can I have morphia serialize this Map properly ? 
I want my Map to be able to store arbitrary object types, and different types can be stored in the same Map... 




Heres' my code example and the exception thrown : 



@Entity
public class TestWithMap {

@Id
private ObjectId id;
private String myProp1;
private Map<String, Object> myMap;

public TestWithMap(ObjectId id, String myProp1, Map<String, Object> myMap) {
super();
this.id = id;
this.myProp1 = myProp1;
this.myMap = myMap;
}

/**
* @return the id
*/
public ObjectId getId() {
return id;
}

/**
* @param id the id to set
*/
public void setId(ObjectId id) {
this.id = id;
}

/**
* @return the myProp1
*/
public String getMyProp1() {
return myProp1;
}

/**
* @param myProp1 the myProp1 to set
*/
public void setMyProp1(String myProp1) {
this.myProp1 = myProp1;
}

/**
* @return the myMap
*/
public Map<String, Object> getMyMap() {
return myMap;
}

/**
* @param myMap the myMap to set
*/
public void setMyMap(Map<String, Object> myMap) {
this.myMap = myMap;
}
}



public class LogEntry {

public static enum SEVERITY { NORMAL, SEVERE, WARN, FATAL};
private String message;
private Timestamp time;
private SEVERITY severity;
public LogEntry(){}
public LogEntry(String message, Timestamp time, SEVERITY severity) {
super();
this.message = message;
this.time = time;
this.severity = severity;
}
/**
* @return the message
*/
public String getMessage() {
return message;
}
/**
* @param message the message to set
*/
public void setMessage(String message) {
this.message = message;
}
/**
* @return the time
*/
public Timestamp getTime() {
return time;
}
/**
* @param time the time to set
*/
public void setTime(Timestamp time) {
this.time = time;
}
/**
* @return the severity
*/
public SEVERITY getSeverity() {
return severity;
}
/**
* @param severity the severity to set
*/
public void setSeverity(SEVERITY severity) {
this.severity = severity;
}
}




//here's how its saved
TestWithMapDao dao = new TestWithMapDaoMorph(TestWithMap.class, getDatastore());
ObjectId oid = new ObjectId();
Map<String, Object> theMap = new HashMap<String, Object>();
theMap.put("fookey", "foovalue");
theMap.put("complxObj", new LogEntry(System.currentTimeMillis(), "hey im the complex object", PRIORITY.important));
TestWithMap twm = new TestWithMap(oid, "i have a map", theMap);
dao.save(twm);




//and the exception :

java.lang.IllegalArgumentException: can't serialize class foo.bar.LogEntry
at org.bson.BasicBSONEncoder._putObjectField(BasicBSONEncoder.java:284)
at org.bson.BasicBSONEncoder.putMap(BasicBSONEncoder.java:324)
at org.bson.BasicBSONEncoder._putObjectField(BasicBSONEncoder.java:246)
at org.bson.BasicBSONEncoder.putObject(BasicBSONEncoder.java:185)
at org.bson.BasicBSONEncoder.putObject(BasicBSONEncoder.java:131)
at com.mongodb.DefaultDBEncoder.writeObject(DefaultDBEncoder.java:33)
at com.mongodb.BSONBinaryWriter.encodeDocument(BSONBinaryWriter.java:339)
at com.mongodb.InsertCommandMessage.writeTheWrites(InsertCommandMessage.java:45)
at com.mongodb.InsertCommandMessage.writeTheWrites(InsertCommandMessage.java:23)
at com.mongodb.BaseWriteCommandMessage.encodeMessageBody(BaseWriteCommandMessage.java:69)
at com.mongodb.BaseWriteCommandMessage.encodeMessageBody(BaseWriteCommandMessage.java:23)
at com.mongodb.RequestMessage.encode(RequestMessage.java:66)
at com.mongodb.BaseWriteCommandMessage.encode(BaseWriteCommandMessage.java:53)
at com.mongodb.DBCollectionImpl.sendWriteCommandMessage(DBCollectionImpl.java:471)
at com.mongodb.DBCollectionImpl.writeWithCommandProtocol(DBCollectionImpl.java:425)
at com.mongodb.DBCollectionImpl.insertWithCommandProtocol(DBCollectionImpl.java:385)
at com.mongodb.DBCollectionImpl.insert(DBCollectionImpl.java:186)
at com.mongodb.DBCollectionImpl.insert(DBCollectionImpl.java:165)
at com.mongodb.DBCollection.insert(DBCollection.java:161)
at com.mongodb.DBCollection.insert(DBCollection.java:107)
at com.mongodb.DBCollection.save(DBCollection.java:966)
at org.mongodb.morphia.DatastoreImpl.save(DatastoreImpl.java:949)
at org.mongodb.morphia.DatastoreImpl.save(DatastoreImpl.java:1013)
at org.mongodb.morphia.DatastoreImpl.save(DatastoreImpl.java:1000)
at org.mongodb.morphia.dao.BasicDAO.save(BasicDAO.java:130)
at foo.bar.Test.testObjectWithMap(Test.java:64)



It appears to me that Morphia simply bypasses the serialization of the values of the Map because the are declared as being of type Object.

Is there any way to have Morphia determine the actual Type of the underlying object at saving time ?

How can I have my map of arbitrary objects stored properly (without having to wrap the values of the Map inside an intermediary object) ?



Thank you ! 

 


Justin Lee

unread,
Sep 11, 2014, 8:37:40 AM9/11/14
to mor...@googlegroups.com
Put @Embedded on LogEntry and see what happens.  I think what's happening is that morphia doesn't know what that type is and so just pass it on to the driver which tries to serialize in to bson and fails.  Annotating it will tell morphia to handle that mapping before the driver gets a hold of it.

--------------------------------

name     : "Justin Lee", 
  title    : "Software Engineer",
  twitter  : "@evanchooly",
  web      : [ "10gen.com", "antwerkz.com" ],
  location : "New York, NY" }

--
You received this message because you are subscribed to the Google Groups "Morphia" group.
To unsubscribe from this group and stop receiving emails from it, send an email to morphia+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

azlists

unread,
Sep 11, 2014, 11:51:22 PM9/11/14
to mor...@googlegroups.com
 Thank you for your answer. 

Annotating LogEntry with @Embedded did not work. I still get the exact same error. 

Did I spot a bug ? 

From my understanding of morphia, this should be supported no ? 

Thanks again for you feedback. 
Message has been deleted
Message has been deleted
Message has been deleted

azlists

unread,
Sep 13, 2014, 3:09:05 AM9/13/14
to mor...@googlegroups.com
Sorry if this message appears multiple time. I've had troubles with google groups.

I've been debugging this a little further and it seems that the LogEntry in the Map is passed to the passthrough converter for some reason. and therefore added "as is" to the DBObject created by Morphia. 


Here is the stack trace up to the point PassthroughConverter is selected : 



Thread [main] (Suspended (breakpoint at line 200 in DefaultConverters))
DefaultConverters.getEncoder(Class) line: 200
DefaultConverters.encode(Class, Object) line: 236
DefaultConverters.encode(Object) line: 232
MapOfValuesConverter.encode(Object, MappedField) line: 62
DefaultConverters.toDBObject(Object, MappedField, DBObject, MapperOptions) line: 210
ValueMapper.toDBObject(Object, MappedField, DBObject, Map<Object,DBObject>, Mapper) line: 19
Mapper.writeMappedField(DBObject, MappedField, Object, Map<Object,DBObject>) line: 630
Mapper.toDBObject(Object, Map<Object,DBObject>, boolean) line: 541
Mapper.toDBObject(Object, Map<Object,DBObject>) line: 523
DatastoreImpl.entityToDBObj(Object, Map<Object,DBObject>) line: 873
DatastoreImpl.save(DBCollection, T, WriteConcern) line: 939
DatastoreImpl.save(T, WriteConcern) line: 1013
DatastoreImpl.save(T) line: 1000
TestWithMapDaoMorph(BasicDAO).save(T) line: 130


 
Is there a way to force morphia to convert this object if it is annotated with @Embedded ? 
For now it seems the annotation is completely ignored when the object is in a collection.

Thank you for your feedback. 



Alex

TAN Yee Fan

unread,
Sep 25, 2014, 2:02:49 PM9/25/14
to mor...@googlegroups.com
What I typically do is to convert the "complex object" into a Map and then store this as the value of the containing Map. Conversion can be achieved using Jackson or some other similar library. Something like:

ObjectMapper mapper = new ObjectMapper(); // Jackson object mapper


ObjectId oid = new ObjectId();

Map<String, Object> theMap = new HashMap<String, Object>();
LogEntry entry = ...
theMap
.put("complxObj", mapper.convertValue(entry, LinkedHashMap.class));


TestWithMap twm = new TestWithMap(oid, "i have a map", theMap);

Then the entity can be saved.
Reply all
Reply to author
Forward
0 new messages