Circe: Невозможно преобразовать из карты [_, Any]

Созданный на 2 мар. 2016  ·  6Комментарии  ·  Источник: circe/circe

Пробуя библиотеку, я понял, что для Any или AnyVal не существует неявных преобразований.

Например, рассмотрим следующий код:

case class Container(m: Map[String, AnyVal])
val container = Container(Map("name" -> "John".asInstanceOf[AnyVal], "age" -> 50))
container.asJson

Это не сработает, потому что не существует неявного сопоставления для AnyVal (или для Any, если на то пошло).

Я пытался понять, как в таких случаях можно написать свои собственные инструкции по неявному преобразованию для преобразования из объектов в JSON и наоборот.

Конечная цель здесь - это что-то вроде возможности конвертировать любой произвольный JSON в Map [String, Any] и наоборот.

Примечание. Если такая функция уже существует, укажите мне правильное направление. Мне не удалось найти это ни в документации, ни по каким-либо другим причинам.

Самый полезный комментарий

@robsonpeixoto Конструкторы Either - это классы case, поэтому generic будет создавать экземпляры для них, как и любые другие классы case (автоматически не предоставляются экземпляры Either , и это не в специальном корпусе).

Если вам нужно представление без тегов, вы можете сделать что-то вроде этого:

import io.circe._, io.circe.generic.auto._, io.circe.jawn._, io.circe.syntax._

implicit val encodeIntOrString: Encoder[Either[Int, String]] =
  Encoder.instance(_.fold(_.asJson, _.asJson))

implicit val decodeIntOrString: Decoder[Either[Int, String]] =
  Decoder[Int].map(Left(_)).or(Decoder[String].map(Right(_)))

А потом:

scala> println(m1.asJson)
{
  "name" : "John",
  "age" : 50
}

scala> decode[Message](m1.asJson.noSpaces)
res1: cats.data.Xor[io.circe.Error,Message] = Right(Map(name -> Right(John), age -> Left(50)))

Это работает для вас?

Все 6 Комментарий

Цирка @ChetanBhasin построена на идее того, чтобы типы Map[String, User] , например, и соответствующие кодировщики выбираются во время компиляции - это бесплатно или опасность отражения во время выполнения. circe совершенно беспомощен, когда дело доходит до Any или AnyVal , где вы не можете сделать ничего разумного со значением _except_, отразите его во время выполнения (если это считается разумным).

Это сделано намеренно и преследует две цели: сделать библиотеку простой и безопасной, избегая любого использования отражения во время выполнения, и способствовать использованию типов. Во многих отношениях Map[String, Any] является противоположностью типобезопасного функционального программирования, и circe стремится сделать возможным и желательным избежать появления таких типов, как Map[String, Any] где-либо в вашей программе.

Конечно, вы можете написать свои собственные экземпляры Encoder и Decoder для таких типов, как Map[String, Any] , но они будут небезопасными, унидиоматическими и, возможно, менее производительными (из-за необходимости отражение во время выполнения), поэтому сама circe почти никогда не предоставит ничего подобного с полки.

Я бы рекомендовал попытаться полностью избежать Map[String, Any] , но если вы застряли в нем и действительно хотите использовать circe (в отличие от библиотеки JSON, которая поддерживает отражение во время выполнения, а это большинство из них: smile: ), вы можете попытаться преобразовать ваши значения Map[String, Any] во что-то более полно типизированное, например, Map[String, Either[Int, String]] , которое circe с радостью закодирует и декодирует.

@travisbrown Спасибо за ответ.

Я могу полностью понять, почему мы можем не захотеть выполнять преобразование между значением Any и JSON и почему пользовательские кодеры - плохая идея.

Однако я думаю, что имеет смысл иметь тип данных, который имеет дело только с примитивами JSON. Например, в то время как единственный способ представить объект JSON пар ключ-значение в vanilla Scala - это использовать Map[String, Any] или Map[String, AnyVal] , у нас может быть что-то вроде Map[String, JsType] .

JsType в этом отношении может быть одним из примитивов JSON (например, String, Long, Boolean, Object, List или null). Однако имеет смысл иметь возможность неявно преобразовывать примитивы JSON в Scala, в то время как обратное может быть достигнуто с помощью типа JSON. Конечно, в этом случае было бы замечательно иметь возможность выполнять некоторые базовые операции с этими значениями (либо путем неявного преобразования, либо путем реализации методов).

Этот подход будет безопасным по типу. Теперь мой единственный вопрос: погода что-то вроде уже существует в библиотеке?
Я понимаю, что это довольно просто реализовать, но было бы здорово, если бы мы могли включить это как функцию.

Мне жаль. Я возился с библиотекой и лучше понимаю это. Закрою вопрос.

@travisbrown Я тестировал Map[String, Either[Int, String]] но он создал json с ключами Left и Right :

import io.circe._, io.circe.generic.auto._, io.circe.parser._, io.circe.syntax._
import io.circe._
import io.circe.generic.auto._
import io.circe.parser._
import io.circe.syntax._
import cats.data.Xor

type Message = Map[String, Either[Int, String]]
object Message {
  def apply(elems: (String, Either[Int, String])*): Map[String, Either[Int, String]] = Map(elems: _*)
}
val m1 = Message("name" -> Right("John"), "age" -> Left(50))
println(m1.asJson)

// Exiting paste mode, now interpreting.

{
  "name" : {
    "Right" : {
      "b" : "John"
    }
  },
  "age" : {
    "Left" : {
      "a" : 50
    }
  }
}

И когда я конвертирую json без этих ключей, декодирование не работает:

// ...
val json: String = """
  {
    "id": 1,
    "name": "Foo",
    "city": "NYC",
    "age": 10,
    "height": 175
  }
"""
val m2 = decode[Message](json)

// Exiting paste mode, now interpreting.
m2: cats.data.Xor[io.circe.Error,Message] = Left(DecodingFailure(CNil, List(El(DownField(id),true,false))))

Это ожидаемое поведение?

@robsonpeixoto Конструкторы Either - это классы case, поэтому generic будет создавать экземпляры для них, как и любые другие классы case (автоматически не предоставляются экземпляры Either , и это не в специальном корпусе).

Если вам нужно представление без тегов, вы можете сделать что-то вроде этого:

import io.circe._, io.circe.generic.auto._, io.circe.jawn._, io.circe.syntax._

implicit val encodeIntOrString: Encoder[Either[Int, String]] =
  Encoder.instance(_.fold(_.asJson, _.asJson))

implicit val decodeIntOrString: Decoder[Either[Int, String]] =
  Decoder[Int].map(Left(_)).or(Decoder[String].map(Right(_)))

А потом:

scala> println(m1.asJson)
{
  "name" : "John",
  "age" : 50
}

scala> decode[Message](m1.asJson.noSpaces)
res1: cats.data.Xor[io.circe.Error,Message] = Right(Map(name -> Right(John), age -> Left(50)))

Это работает для вас?

Хорошо, @travisbrown. Спасибо

Была ли эта страница полезной?
0 / 5 - 0 рейтинги