You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动