如何使用 Anorm 将 JSON 插入到 postgres table 中?

How do I insert JSON into a postgres table using Anorm?

我在尝试将 JSON 字符串插入 JSON 列时遇到运行时异常。我的字符串看起来像 """{"Events": []}""",table 有一列定义为 status JSONB NOT NULL。我可以从命令行将字符串插入 table 没问题。我已经定义了一个方法来做插入:

    import play.api.libs.json._
    import anorm._
    import anorm.postgresql._

    def createStatus(
      status: String,
      created: LocalDateTime = LocalDateTime.now())(implicit c: SQLConnection): Unit = {
      SQL(s"""
             |INSERT INTO status_feed
             |  (status, created)
             |VALUES
             |  ({status}, {created})
             |""".stripMargin)
        .on(
          'status -> Json.parse("{}"), // n.b. would be Json.parse(status) but this provides a concise error message
          'created -> created)
        .execute()
    }

调用它会出现以下错误:

TypeDoesNotMatch(Cannot convert {}: org.postgresql.util.PGobject to String for column ColumnName(status_feed.status,Some(status)))
anorm.AnormException: TypeDoesNotMatch(Cannot convert {}: org.postgresql.util.PGobject to String for column ColumnName(status_feed.status,Some(status)))

我已经为这个问题做了很多搜索,但是我找不到关于这个特定用例的任何东西 - 大多数都是将 json 列提取到案例 类 中。我使用 spray-json 的 JsValue、play 的 JsValue 尝试了稍微不同的格式,只需按原样传递字符串并在查询中使用 ::JSONB 进行转换,它们都会给出相同的错误。

更新:这是创建 table:

的 SQL
  CREATE TABLE status_feed (
    id SERIAL PRIMARY KEY,
    status JSONB NOT NULL,
    created TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW()
  )

错误不在给 .executeInsert 的值上,而是在 INSERT 结果(插入的键)的解析上。

import java.sql._

// postgres=# CREATE TABLE test(foo JSONB NOT NULL);

val jdbcUrl = "jdbc:postgresql://localhost:32769/postgres"
val props = new java.util.Properties()
props.setProperty("user", "postgres")
props.setProperty("password", "mysecretpassword")

implicit val con = DriverManager.getConnection(jdbcUrl, props)

import anorm._, postgresql._
import play.api.libs.json._

SQL"""INSERT INTO test(foo) VALUES(${Json.obj("foo" -> 1)})""".
  executeInsert(SqlParser.scalar[JsValue].singleOpt)

// Option[play.api.libs.json.JsValue] = Some({"foo":1})

/*
postgres=# SELECT * FROM test ;
    foo     
------------
 {"foo": 1}
 */

BTW, the plain string interpolation is useless.

事实证明 cchantep 是正确的,它是我使用的解析器。我正在使用的测试框架吞没了堆栈跟踪,我认为问题出在插入上,但实际上爆炸的是我使用解析器的测试中的下一行。

案例 class 和解析器定义为:

case class StatusFeed(
  status: String,
  created: LocalDateTime) {
  val ItemsStatus: Status = status.parseJson.convertTo[Status]
}

object StatusFeed extends DefaultJsonProtocol {
  val fields: String = sqlFields[StatusFeed]() // helper function that results in "created, status"
  // used in SQL as RETURNING ${StatusFeed.fields}
  val parser: RowParser[StatusFeed] =
    Macro.namedParser[StatusFeed](Macro.ColumnNaming.SnakeCase)
  // json formatter for Status
}

根据定义,解析器尝试将结果集中的 JSONB 列读入字符串 status。将 fields 更改为 val fields: String = "created, status::TEXT" 可以解决问题,但转换可能会很昂贵。或者,将 status 定义为 JsValue 而不是 String 并为异常提供隐式(改编自 this answer 以使用 spray-json)可解决此问题:

  implicit def columnToJsValue: Column[JsValue] = anorm.Column.nonNull[JsValue] { (value, meta) =>
    val MetaDataItem(qualified, nullable, clazz) = meta
    value match {
      case json: org.postgresql.util.PGobject => Right(json.getValue.parseJson)
      case _ =>
        Left(TypeDoesNotMatch(
          s"Cannot convert $value: ${value.asInstanceOf[AnyRef].getClass} to Json for column $qualified"))
    }
  }