scala spray json необязательный параметр идентификатора в классе сущностей

Я использую библиотеку Salat для сериализации классов case как объектов mongoDb. Мой файл Item.scala выглядит так:

case class Item(_id: String = (new ObjectId).toString, itemId: Int, var name: String, var active: Boolean) extends WithId {
  override def id: ObjectId = new ObjectId(_id)
}

object Item extends MongoDb[Item] with MongoDao[Item] {
  override def collectionName: String = "items"
}

object ItemJsonProtocol extends DefaultJsonProtocol {
  implicit val itemFormat = jsonFormat4(Item.apply)
}

Теперь я использую его для публикации объектов Item в формате Json через Spray HTTP. Я хотел бы вызвать его следующим образом:

curl.exe -H "Content-Type: application/json" -X PUT -d "{\"itemId\":
1, \"active\":true, \"name\" : \"test\"}" http://localhost:8080/items/

надеясь, что он предоставит сгенерированный идентификатор, если я его не предоставлю.

Однако после вызова команды curl я получаю сообщение об ошибке:

Содержимое запроса было искажено: в объекте отсутствует обязательный член '_id'

Есть ли способ пометить поле _id как необязательное, не делая из него Option (это поле всегда будет установлено) и определяя пользовательский JsonFormat, таким образом (де) сериализуя объект самостоятельно?

Я прочитал этот пост: https://stackoverflow.com/a/10820293/1042869, но мне было интересно, есть любой другой способ сделать это, так как у меня много случаев полей _id. Также был комментарий, в котором говорилось: «Вы, но вы можете присвоить этому полю значение по умолчанию в определении класса case, поэтому, если поле не находится в json, оно присвоит ему значение по умолчанию.», но, как вы можете видеть здесь это не работает.

Лучший, Марчин


person Marcin    schedule 01.07.2014    source источник
comment
что, если бы он был определен case class Item(_id: Option[String] = Some((new ObjectId).toString) ...?   -  person Gangstead    schedule 01.07.2014
comment
Ну не вижу смысла делать _id Option, если он всегда есть.   -  person Marcin    schedule 01.07.2014
comment
Но его нет, если вы хотите использовать ту же модель для создания объектов, что и для извлечения объектов. Другой вариант — создать отдельную модель для создания элемента (без идентификатора).   -  person Gangstead    schedule 01.07.2014


Ответы (2)


Поэтому я решил проблему, написав собственный RootJsonFormat::

  implicit object ItemJsonFormat extends RootJsonFormat[Item] {
    override def read(json: JsValue): Item = json.asJsObject.getFields("_id", "itemId", "name", "active") match {
      case Seq(JsString(_id), JsNumber(itemId), JsString(name), JsBoolean(active)) => Item(_id = _id, itemId = itemId.toInt, name = name, active = active)
      case Seq(JsNumber(itemId), JsString(name), JsBoolean(active)) => Item(itemId = itemId.toInt, name = name, active = active)
      case _ => throw new DeserializationException("Item expected")
    }
    override def write(obj: Item): JsValue = JsObject(
      "_id" -> JsString(obj._id),
      "itemId" -> JsNumber(obj.itemId),
      "name" -> JsString(obj.name),
      "active" -> JsBoolean(obj.active)
    )
  }

По сути, он проверяет, получили ли мы _id в json, если да, то мы используем его для создания объекта, а в другом случае сохраняем автоматически сгенерированное поле идентификатора.

Еще одна вещь, которая может вызвать некоторые проблемы, но, на мой взгляд, заслуживает упоминания где-нибудь - если у кого-то есть проблема с вложенными объектами ("непримитивными" типами) - я советую использовать .toJson в записи def (например, obj.time.toJson, где obj.time — это DateTime для jodatime) и определение JsValue .convertTo[T] в чтении, например time = JsString(time). convertTo[DateTime]. Чтобы это работало, должны быть определены неявные форматы json для этих «непримитивных» объектов.

Лучший, Марчин

person Marcin    schedule 03.07.2014
comment
Этот ответ полностью действителен, если у вас нет необязательного параметра рядом с обязательным параметром того же типа, если у вас есть, проверьте мой ответ - person Adrian Lopez; 04.02.2015

Я бы использовал это решение:

case class Item(_id: Option[String], itemId: Int, var name: String, var active: Boolean)

implicit object ItemJsonFormat extends RootJsonFormat[Item] {
   override def read(value: JsValue) = {
     val _id = fromField[Option[String]](value, "_id")
     val itemId = fromField[Int](value, "itemId")
     val expires = fromField[Long](value, "expires")
     val name = fromField[String](value, "name")
     val active = fromField[Boolean](value, "active")
     Item(_id, itemId, name, active)
   }
   override def write(obj: Item): JsValue = JsObject(
     "_id" -> JsString(obj._id),
     "itemId" -> JsNumber(obj.itemId),
     "name" -> JsString(obj.name),
     "active" -> JsBoolean(obj.active)
   )
}

Преимущество по сравнению с решением json.asJsObject.getFields заключается в том, что вы лучше контролируете то, что принимается в случае неопределенного идентификатора. Пример, когда это не сработает, следующий:

  • itemId — это строка, такая же, как id
  • id определен, но itemId нет

В этом случае случай совпадения будет интерпретировать указанный идентификатор как itemId и не поймать ошибку.

person Adrian Lopez    schedule 04.02.2015