Scala преобразует JSON в один из нескольких типов

#json #scala

#json #scala

Вопрос:

Я получаю некоторый JSON из потока.

Объект может быть представлен одним из нескольких типов scala. Например: {"userid":"blerk","name":"Fred"} было бы User(userId:String, userName:String)

{"groupId":"zerk","name":"Accounting"} было бы Group(groupId:String,groupName:String}

(реальные объекты содержат гораздо больше элементов).

Я использую spray-json, если это имеет значение, но я хочу знать следующее:

Я хотел бы иметь функцию «convert», которая последовательно выполняет Try(myJson.convertTo[T]) до тех пор, пока она не будет успешной, и возвращает значение типа T .

Моя цель — избежать использования какой-либо эвристики сопоставления строк, чтобы заранее выяснить, какой тип это может быть, и просто иметь возможность добавлять новый тип в список, когда появляется новый случай. Я также хотел бы избежать такого кода, как

 val t1=Try(myJson.convertTo[User])
if(t1.isSuccess) return t1.get
val t2=Try(myJson.convertTo[Group])
if(t2.isSuccess) return t2.get
  

К сожалению, у меня возникли проблемы с тем, чтобы обернуть мой маленький мозг вокруг этого, и я был бы благодарен за любую помощь.

Ответ №1:

Вы можете использовать Try :T](default:=>scala.util.Try[U]):scala.util.Try[U]» rel=»nofollow»> orElse метод, чтобы сделать то, что вы просите. Вот первая попытка:

 import scala.util.Try
import spray.json._, DefaultJsonProtocol._

case class User(userId: String, userName: String)
case class Group(groupId: String, groupName: String)

implicit val userFormat: JsonFormat[User] = jsonFormat2(User)
implicit val groupFormat: JsonFormat[Group] = jsonFormat2(Group)

val groupJson = """{ "groupId": "zerk", "groupName": "Accounting" }""".parseJson
val userJson = """{ "userId": "foo", "userName": "Foo McBar" }""".parseJson

val result = Try(groupJson.convertTo[User]).orElse(Try(groupJson.convertTo[Group]))
  

Если вам нужно попробовать более двух типов, вы можете поместить попытки в последовательность и свернуть их с помощью _ orElse _ .

Это сработает, но даст вам результат типа Try[Any] , который практически бесполезен, если вы заботитесь о безопасности типов, хотя вы всегда можете сопоставить результат с шаблоном, если вы готовы смириться с тем фактом, что в принципе у вас может быть что угодно.

Лучшим подходом является декодирование в Either :

 scala> groupJson.convertTo[Either[Group, User]]
res3: Either[Group,User] = Left(Group(zerk,Accounting))

scala> userJson.convertTo[Either[Group, User]]
res4: Either[Group,User] = Right(User(foo,Foo McBar))
  

Это также обобщается на более чем два типа, хотя вам нужно вложить Either s:

 scala> userJson.convertTo[Either[Either[Group, String], User]]
res5: Either[Either[Group,String],User] = Right(User(foo,Foo McBar))
  

Если вам нужен более чистый способ работы с результатом, который может быть в конечном итоге одним из более чем двух типов, Shapeless Coproduct — это хорошее принципиальное решение. Вам нужно будет написать свой собственный формат для сопутствующих продуктов, но это относительно простое упражнение.

Тем не менее, это крайний путь обеспечения безопасности типов. Если вам все равно, просто используйте orElse .

Комментарии:

1. Спасибо; поскольку это учебное упражнение, я не готов отказываться от безопасности типов (по крайней мере, пока). И поскольку существует довольно много возможных типов, вложенный Either маршрут также не является полностью удовлетворительным ;-). Я ценю ссылку на Shapeless, так как их ShapelessStream похоже, что это тоже может быть полезно!