Bien que similaire à Convertir DBObject en POJO à l'aide du pilote Java MongoDB ma question est différente en ce que je suis {spécifiquement intéressé à utiliser Jackson pour le mappage.
J'ai un objet que je veux convertir en une instance de Mongo DBObject. Je souhaite utiliser le framework JSON de Jackson pour faire le travail.
Une façon de le faire est:
DBObject dbo = (DBObject)JSON.parse(m_objectMapper.writeValueAsString(entity));
Cependant, selon https://github.com/FasterXML/jackson-docs/wiki/Presentation:-Jackson-Performance , c'est la pire façon de faire. Donc, je cherche une alternative. Dans l'idéal, j'aimerais pouvoir accéder au pipeline de génération JSON et peupler une instance DBObject
à la volée. Cela est possible car, dans mon cas, la cible est une instance BasicDBObject
, qui implémente l'interface Map. Ainsi, il devrait s'intégrer facilement dans le pipeline.
Maintenant, je sais que je peux convertir un objet en carte à l'aide de la fonction ObjectMapper.convertValue
, puis convertir de manière récursive la carte en une instance BasicDBObject
à l'aide du constructeur de carte du type BasicDBObject
. Mais, je veux savoir si je peux éliminer la carte intermédiaire et créer directement la BasicDBObject
.
Notez que, étant donné que BasicDBObject
est essentiellement une carte, la conversion opposée, à savoir une variable scalaire DBObject
en POJO, est triviale et devrait être relativement efficace:
DBObject dbo = getDBO();
Class clazz = getObjectClass();
Object pojo = m_objectMapper.convertValue(dbo, clazz);
Enfin, mon POJO n’a pas d’annotations JSON et j’aimerais qu’il en soit ainsi.
Vous pouvez probablement utiliser les annotations Mixin pour annoter votre POJO et la variable BasicDBObject
(ou DBObject
). Les annotations ne posent donc pas de problème. Puisque BasicDBOject
est une carte, vous pouvez utiliser @JsonAnySetter
sur la méthode put.
m_objectMapper.addMixInAnnotations(YourMixIn.class, BasicDBObject.class);
public interface YourMixIn.class {
@JsonAnySetter
void put(String key, Object value);
}
C’est tout ce que je peux trouver puisque je n’ai aucune expérience avec MongoDB Object.
Update:MixIn sont fondamentalement un mécanisme de Jackson permettant d’ajouter des annotations à une classe sans modifier ladite classe. Cela convient parfaitement lorsque vous n'avez pas le contrôle sur la classe que vous souhaitez marshaler (par exemple, si elle provient d'un fichier JAR externe) ou lorsque vous ne souhaitez pas encombrer vos classes d'annotation.
Dans votre cas, vous avez dit que BasicDBObject
implémente l'interface Map
, de sorte que la classe a la méthode put
, telle que définie par l'interface de carte. En ajoutant @JsonAnySetter à cette méthode, vous indiquez à Jackson que chaque fois qu'il trouve une propriété qu'il ne connaît pas après l'introspection de la classe, il utilise la méthode pour l'insérer dans l'objet. La clé est le nom de la propriété et la valeur est bien la valeur de la propriété.
Tout cela combiné fait disparaître la carte intermédiaire, puisque Jackson convertira directement en BasicDBOject
car il sait maintenant comment désérialiser cette classe de Json. Avec cette configuration, vous pouvez faire:
DBObject dbo = m_objectMapper.convertValue(pojo, BasicDBObject.class);
Notez que je n'ai pas testé cela parce que je ne travaille pas avec MongoDB, il pourrait donc y avoir quelques difficultés. Cependant, j'ai utilisé le même mécanisme pour des cas d'utilisation similaires sans aucun problème. YMMV selon les classes.
Voici un exemple de sérialiseur simple (écrit en scala) de POJO à BsonDocument qui pourrait être utilisé avec la version 3 du pilote Mongo . Le dé-sérialiseur serait un peu plus difficile à écrire.
Créez un objet BsonObjectGenerator
qui effectuerait une sérialisation en streaming directement vers Mongo Bson:
val generator = new BsonObjectGenerator
mapper.writeValue(generator, POJO)
generator.result()
Voici le code pour un sérialiseur:
class BsonObjectGenerator extends JsonGenerator {
sealed trait MongoJsonStreamContext extends JsonStreamContext
case class MongoRoot(root: BsonDocument = BsonDocument()) extends MongoJsonStreamContext {
_type = JsonStreamContext.TYPE_ROOT
override def getCurrentName: String = null
override def getParent: MongoJsonStreamContext = null
}
case class MongoArray(parent: MongoJsonStreamContext, arr: BsonArray = BsonArray()) extends MongoJsonStreamContext {
_type = JsonStreamContext.TYPE_ARRAY
override def getCurrentName: String = null
override def getParent: MongoJsonStreamContext = parent
}
case class MongoObject(name: String, parent: MongoJsonStreamContext, obj: BsonDocument = BsonDocument()) extends MongoJsonStreamContext {
_type = JsonStreamContext.TYPE_OBJECT
override def getCurrentName: String = name
override def getParent: MongoJsonStreamContext = parent
}
private val root = MongoRoot()
private var node: MongoJsonStreamContext = root
private var fieldName: String = _
def result(): BsonDocument = root.root
private def unsupported(): Nothing = throw new UnsupportedOperationException
override def disable(f: Feature): JsonGenerator = this
override def writeStartArray(): Unit = {
val array = new BsonArray
node match {
case MongoRoot(o) =>
o.append(fieldName, array)
fieldName = null
case MongoArray(_, a) =>
a.add(array)
case MongoObject(_, _, o) =>
o.append(fieldName, array)
fieldName = null
}
node = MongoArray(node, array)
}
private def writeBsonValue(value: BsonValue): Unit = node match {
case MongoRoot(o) =>
o.append(fieldName, value)
fieldName = null
case MongoArray(_, a) =>
a.add(value)
case MongoObject(_, _, o) =>
o.append(fieldName, value)
fieldName = null
}
private def writeBsonString(text: String): Unit = {
writeBsonValue(BsonString(text))
}
override def writeString(text: String): Unit = writeBsonString(text)
override def writeString(text: Array[Char], offset: Int, len: Int): Unit = writeBsonString(new String(text, offset, len))
override def writeString(text: SerializableString): Unit = writeBsonString(text.getValue)
private def writeBsonFieldName(name: String): Unit = {
fieldName = name
}
override def writeFieldName(name: String): Unit = writeBsonFieldName(name)
override def writeFieldName(name: SerializableString): Unit = writeBsonFieldName(name.getValue)
override def setCodec(oc: ObjectCodec): JsonGenerator = this
override def useDefaultPrettyPrinter(): JsonGenerator = this
override def getFeatureMask: Int = 0
private def writeBsonBinary(data: Array[Byte]): Unit = {
writeBsonValue(BsonBinary(data))
}
override def writeBinary(bv: Base64Variant, data: Array[Byte], offset: Int, len: Int): Unit = {
val res = if (offset != 0 || len != data.length) {
val subset = new Array[Byte](len)
System.arraycopy(data, offset, subset, 0, len)
subset
} else {
data
}
writeBsonBinary(res)
}
override def writeBinary(bv: Base64Variant, data: InputStream, dataLength: Int): Int = unsupported()
override def isEnabled(f: Feature): Boolean = false
override def writeRawUTF8String(text: Array[Byte], offset: Int, length: Int): Unit = writeBsonString(new String(text, offset, length, "UTF-8"))
override def writeRaw(text: String): Unit = unsupported()
override def writeRaw(text: String, offset: Int, len: Int): Unit = unsupported()
override def writeRaw(text: Array[Char], offset: Int, len: Int): Unit = unsupported()
override def writeRaw(c: Char): Unit = unsupported()
override def flush(): Unit = ()
override def writeRawValue(text: String): Unit = writeBsonString(text)
override def writeRawValue(text: String, offset: Int, len: Int): Unit = writeBsonString(text.substring(offset, offset + len))
override def writeRawValue(text: Array[Char], offset: Int, len: Int): Unit = writeBsonString(new String(text, offset, len))
override def writeBoolean(state: Boolean): Unit = {
writeBsonValue(BsonBoolean(state))
}
override def writeStartObject(): Unit = {
node = node match {
case p@MongoRoot(o) =>
MongoObject(null, p, o)
case p@MongoArray(_, a) =>
val doc = new BsonDocument
a.add(doc)
MongoObject(null, p, doc)
case p@MongoObject(_, _, o) =>
val doc = new BsonDocument
val f = fieldName
o.append(f, doc)
fieldName = null
MongoObject(f, p, doc)
}
}
override def writeObject(pojo: scala.Any): Unit = unsupported()
override def enable(f: Feature): JsonGenerator = this
override def writeEndArray(): Unit = {
node = node match {
case MongoRoot(_) => unsupported()
case MongoArray(p, a) => p
case MongoObject(_, _, _) => unsupported()
}
}
override def writeUTF8String(text: Array[Byte], offset: Int, length: Int): Unit = writeBsonString(new String(text, offset, length, "UTF-8"))
override def close(): Unit = ()
override def writeTree(rootNode: TreeNode): Unit = unsupported()
override def setFeatureMask(values: Int): JsonGenerator = this
override def isClosed: Boolean = unsupported()
override def writeNull(): Unit = {
writeBsonValue(BsonNull())
}
override def writeNumber(v: Int): Unit = {
writeBsonValue(BsonInt32(v))
}
override def writeNumber(v: Long): Unit = {
writeBsonValue(BsonInt64(v))
}
override def writeNumber(v: BigInteger): Unit = unsupported()
override def writeNumber(v: Double): Unit = {
writeBsonValue(BsonDouble(v))
}
override def writeNumber(v: Float): Unit = {
writeBsonValue(BsonDouble(v))
}
override def writeNumber(v: BigDecimal): Unit = unsupported()
override def writeNumber(encodedValue: String): Unit = unsupported()
override def version(): Version = unsupported()
override def getCodec: ObjectCodec = unsupported()
override def getOutputContext: JsonStreamContext = node
override def writeEndObject(): Unit = {
node = node match {
case p@MongoRoot(_) => p
case MongoArray(p, a) => unsupported()
case MongoObject(_, p, _) => p
}
}
}
Vous pourriez être intéressé à vérifier comment jongo le fait. Il est open source et le code peut être trouvé sur github . Ou vous pouvez aussi simplement utiliser leur bibliothèque. J'utilise un mélange de jongo et de DBObject
s quand j'ai besoin de plus de souplesse.
Ils prétendent qu'ils sont (presque) aussi rapides que d’utiliser le pilote Java directement, alors je suppose que leur méthode est efficace.
J'utilise la classe utilitaire de petit assistant ci-dessous qui est inspirée de leur base de code et utilise un mélange de Jongo (le MongoBsonFactory
) et de Jackson pour effectuer la conversion entre DBObjects et POJO. Notez que la méthode getDbObject
crée une copie complète de DBObject pour la rendre modifiable. Si vous n'avez pas besoin de personnaliser quoi que ce soit, vous pouvez supprimer cette partie et améliorer les performances.
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
import com.mongodb.BasicDBObject;
import com.mongodb.DBEncoder;
import com.mongodb.DBObject;
import com.mongodb.DefaultDBEncoder;
import com.mongodb.LazyWriteableDBObject;
import Java.io.ByteArrayOutputStream;
import Java.io.IOException;
import org.bson.LazyBSONCallback;
import org.bson.io.BasicOutputBuffer;
import org.bson.io.OutputBuffer;
import org.jongo.marshall.jackson.bson4jackson.MongoBsonFactory;
public class JongoUtils {
private final static ObjectMapper mapper = new ObjectMapper(MongoBsonFactory.createFactory());
static {
mapper.setVisibilityChecker(VisibilityChecker.Std.defaultInstance().withFieldVisibility(
JsonAutoDetect.Visibility.ANY));
}
public static DBObject getDbObject(Object o) throws IOException {
ObjectWriter writer = mapper.writer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
writer.writeValue(baos, o);
DBObject dbo = new LazyWriteableDBObject(baos.toByteArray(), new LazyBSONCallback());
//turn it into a proper DBObject otherwise it can't be edited.
DBObject result = new BasicDBObject();
result.putAll(dbo);
return result;
}
public static <T> T getPojo(DBObject o, Class<T> clazz) throws IOException {
ObjectReader reader = mapper.reader(clazz);
DBEncoder dbEncoder = DefaultDBEncoder.FACTORY.create();
OutputBuffer buffer = new BasicOutputBuffer();
dbEncoder.writeObject(buffer, o);
T pojo = reader.readValue(buffer.toByteArray());
return pojo;
}
}
Exemple d'utilisation:
Pojo pojo = new Pojo(...);
DBObject o = JongoUtils.getDbObject(pojo);
//you can customise it if you want:
o.put("_id", pojo.getId());
Je comprends que c’est une très vieille question, mais si on me l’avait posée aujourd’hui, je recommanderais plutôt le prise en charge intégrée de POJO sur le pilote officiel Mongo Java.
Voici une mise à jour de la réponse d’assylias qui ne nécessite pas Jongo et qui est compatible avec les pilotes Mongo 3.x. Il gère également les graphiques d'objet imbriqués, je ne pouvais pas le faire fonctionner avec LazyWritableDBObject
qui a été supprimé dans les pilotes mongo 3.x de toute façon.
L'idée est de dire à Jackson comment sérialiser un objet sur un tableau d'octets BSON, puis de désérialiser le tableau d'octets BSON en BasicDBObject
. Je suis sûr que vous pouvez trouver une API de bas niveau dans les pilotes mongo-Java si vous souhaitez envoyer les octets BSON directement à la base de données. Vous aurez besoin d’une dépendance de bson4jackson pour que ObjectMapper
puisse sérialiser BSON lorsque vous appelez writeValues(ByteArrayOutputStream, Object)
:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import de.undercouch.bson4jackson.BsonFactory;
import de.undercouch.bson4jackson.BsonParser;
import org.bson.BSON;
import org.bson.BSONObject;
import Java.io.ByteArrayOutputStream;
import Java.io.IOException;
public class MongoUtils {
private static ObjectMapper mapper;
static {
BsonFactory bsonFactory = new BsonFactory();
bsonFactory.enable(BsonParser.Feature.HONOR_DOCUMENT_LENGTH);
mapper = new ObjectMapper(bsonFactory);
}
public static DBObject getDbObject(Object o) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
mapper.writeValue(baos, o);
BSONObject decode = BSON.decode(baos.toByteArray());
return new BasicDBObject(decode.toMap());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}