ReactiveMongo能否完整处理扩展JSON转BSON?转换异常求助
解决ReactiveMongo中Play JSON扩展JSON转BSON类型的问题
我之前也碰到过一模一样的问题——默认的json2bson转换只会处理常规JSON结构,完全不识别MongoDB扩展JSON里的$date、$binary这类特殊操作符,所以只有_id因为MongoDB的特殊规则被转对了,其他类型都保留了嵌套的扩展JSON结构。下面是我验证过的几种解决办法:
方法一:直接用ReactiveMongo的ExtendedJson解析器处理字符串
如果你的源数据是扩展JSON字符串,别先转成Play JsObject,直接用ReactiveMongo自带的ExtendedJson解析成BSONDocument,这是最靠谱的方式:
import reactivemongo.api.bson._ import reactivemongo.api.bson.collection.BSONCollection // 你的扩展JSON字符串 val extendedJsonStr = """{"_id":{"$oid":"5f3403dc7e562db8e0aced6b"},"some_datetime":{"$date":{"$date":1597841586927}}}""" // 解析成BSONDocument,处理所有扩展JSON类型 val parsedBson = ExtendedJson.parse(extendedJsonStr) match { case Right(bsonDoc) => bsonDoc case Left(err) => throw new RuntimeException(s"解析扩展JSON失败: $err") } // 插入到BSON集合 val collection: BSONCollection = ... // 你的集合实例 collection.insert.one(parsedBson)
这样解析出来的BSON会自动把$oid转成BSONObjectID,$date转成BSONDateTime,$binary+$type:"04"转成MongoDB能识别的UUID类型,插入后在shell里看就是正常的ISODate()和UUID格式了。
方法二:手动转换已有的Play JsObject
如果你已经拿到了Play JsObject,那得自己写个转换函数来处理这些特殊的扩展JSON字段:
import reactivemongo.play.json._ import reactivemongo.play.json.compat._ import reactivemongo.api.bson._ import play.api.libs.json._ import java.util.UUID import java.util.Base64 def convertExtendedJson(jsObj: JsObject): BSONDocument = { BSONDocument(jsObj.fields.map { // 处理ObjectID case (key, JsObject(fields)) if fields.contains("$oid") => key -> BSONObjectID(fields("$oid").as[String]) // 处理DateTime,兼容两种$date格式:{"$date":123} 和 {"$date":{"$date":123}} case (key, JsObject(fields)) if fields.contains("$date") => val timestamp = fields("$date") match { case JsNumber(ts) => ts.toLong case JsObject(nested) => nested("$date").as[Long] case _ => throw new IllegalArgumentException(s"不合法的$date格式") } key -> BSONDateTime(timestamp) // 处理UUID类型的$binary($type:"04") case (key, JsObject(fields)) if fields.contains("$binary") && fields("$type").as[String] == "04" => val binaryBytes = Base64.getDecoder.decode(fields("$binary").as[String]) val uuid = UUID.fromString(new String(binaryBytes)) key -> BSONBinary(uuid, Subtype.UuidSubtype) // 其他常规类型用默认转换 case (key, value) => key -> json2bson.reads(value).getOrElse(BSONNull) }) } // 使用示例 val yourJsObj: JsObject = ... // 你的Play JSON对象 val bsonDoc = convertExtendedJson(yourJsObj) collection.insert.one(bsonDoc)
为什么默认的json2bson不管用?
说白了,json2bson就是做“常规JSON到BSON”的字面转换——比如你传一个带$date键的JsObject,它就转成一个键为$date的BSON文档,根本不知道这是MongoDB的扩展JSON语法。只有_id字段是因为MongoDB在插入时会自动检测并转换,其他字段没这个待遇。
额外提醒
- 尽量用
BSONCollection而不是JSONCollection,前者对BSON类型的支持更直接,不容易踩坑。 - 处理UUID的时候要注意
$type的值,04是MongoDB标准的UUID子类型,转换时一定要指定Subtype.UuidSubtype,不然MongoDB不会把它当成UUID类型存储。
内容的提问来源于stack exchange,提问作者Florentin Hennecker




