如何使用 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"))
}
}
我在尝试将 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"))
}
}