scala-play 2.4.11, можно ли десериализовать карту с классом case в качестве ключа?

Я пытаюсь справиться с play-json, и это не очень хорошо. Вот мои кейс-классы

sealed case class Items(items: List[Item])

sealed case class Item(path: String, itemCounters: Map[ItemCategory, Long])

sealed case class ItemCategory(repository: Repository)

sealed case class Repository(env: String)

Здесь я пытаюсь разобрать json:

implicit lazy val repositoryFormat = Json.format[Repository]
implicit lazy val itemCategoryFormat = Json.format[ItemCategory]
implicit lazy val itemFormat = Json.format[Item]
implicit lazy val itemsFormat = Json.format[Items]

Json.parse(str).as[Items]

Я получаю исключение: Неявный формат для Map[ItemCategory,Long] недоступен.

Почему?


person Capacytron    schedule 24.05.2018    source источник
comment
Не могли бы вы также опубликовать JSON?   -  person Antot    schedule 25.05.2018
comment
Ошибка во время компиляции :(   -  person Capacytron    schedule 25.05.2018
comment
В изоляции этот код работает, я думаю, что в вашем примере чего-то не хватает/отличается.   -  person Andy Hayden    schedule 25.05.2018


Ответы (1)


Это не удается, потому что play-json не понимает, как десериализовать свойство itemCounters: Map[ItemCategory, Long] в Item.

Действительно, карты JSON можно обрабатывать напрямую, если ключом является String. Но это становится немного сложнее с другими структурированными объектами в ключах, такими как ItemCategory в вопросе. Конечно, JSON с таким ключом не может быть { "repository": { "env": "demo" } }: 1!

Итак, нам нужно четко указать десериализацию такого рода карты. Я предполагаю, что ключом для ItemCategory является базовое значение ItemCategory.repository.env, но это может быть любое другое свойство в зависимости от вашей эффективной модели данных.

Мы предоставляем реализацию Reads для такого типа карты:

implicit lazy val itemCategoryMapReads = new Reads[Map[ItemCategory, Long]] {
  override def reads(jsVal: JsValue): JsResult[Map[ItemCategory, Long]] = {
    JsSuccess(
      // the original string -> number map is translated into ItemCategory -> Long
      jsVal.as[Map[String, Long]].map{
        case (category, id) => (ItemCategory(Repository(category)), id)
      }
    )
  }
}

И соответствующий Format (с заглушкой для Writes, которая нам сейчас не нужна):

implicit lazy val itemCategoryMapFormat = Format(itemCategoryMapReads, (catMap: Map[ItemCategory, Long]) => ???)

Базовый JSON теперь отображается правильно:

val strItemCat =
  """
    | {
    |   "rep1": 1,
    |   "rep2": 2,
    |   "rep3": 3
    | }
  """.stripMargin

println(Json.parse(strItemCat).as[Map[ItemCategory, Long]])
// Map(ItemCategory(Repository(rep1)) -> 1, ItemCategory(Repository(rep2)) -> 2, ItemCategory(Repository(rep3)) -> 3)

Для других case-классов уже определенные вами простые форматы должны работать правильно, при условии, что они объявлены в порядке от наиболее к наименее специфичному (от Repository до Items).

person Antot    schedule 25.05.2018
comment
Спасибо, это было плохое решение поместить объект в ключ карты. Спасибо. Я не думал об этом. - person Capacytron; 25.05.2018