Пробуя библиотеку, я понял, что для 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] и наоборот.
Примечание. Если такая функция уже существует, укажите мне правильное направление. Мне не удалось найти это ни в документации, ни по каким-либо другим причинам.
Цирка @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. Спасибо
Самый полезный комментарий
@robsonpeixoto Конструкторы
Either
- это классы case, поэтомуgeneric
будет создавать экземпляры для них, как и любые другие классы case (автоматически не предоставляются экземплярыEither
, и это не в специальном корпусе).Если вам нужно представление без тегов, вы можете сделать что-то вроде этого:
А потом:
Это работает для вас?