Я новичок в Shapeless и извиняюсь, если не вижу простого решения.
Представьте, что у нас есть класс данных
case class Test(x: Int, y: String, z: Double) extends Row
Json (не содержит все поляcase case)
{ "x": 10, "y": "foo" }
и особый класс case для декодирования json в
case class UpdateRequest[T <: Row](updates: List[Update[T]])
Update [Row] - это запечатанная черта, помогающая выполнять компонуемое обновление в Slick, это огромная возможность опубликовать здесьи на самом деле это не проблема.
Цель состоит в том, чтобы проанализировать json (я использую circe) и проверить, существует ли каждое поле json в классе case, предоставленном в качестве параметра типа для UpdateRequest, и попытаться декодировать значение json с помощьюtype получен из case-класса.
Например, мне нужно работать с чем-то вроде
parse(json).as[UpdateRequest[Test]] ...
Поэтому нам нужен собственный декодер, смешанный с бесформенным.
Это общее описание, если выпоказать полное решение было бы здорово, но текущая проблема в том, что я не могу найти по имени конкретное поле из списка полей
Например, я могу декодировать определенное поле, например
def decode[T, U](s: (Symbol with Tagged[U], T), c: HCursor)(implicit decoder: Decoder[T]) = {
c.downField(s._1.name).as[T]
}
val test = Test(1, "foo", 1.5)
val lg = LabelledGeneric[Test]
val fields = Fields[lg.Repr].apply(lg.to(test))
decode(fields.head)
Но как идтипо всем полямсначала найду по имени?
Я предполагаю, что это может быть как
def decode[...](fields: [...], c: HCursor, fieldName: String)(implicit decoder: Decoder[T]) = {
// try to find field by name, if exists try to decode
...
}
Заранее спасибо за помощь.
РЕДАКТИРОВАТЬ
Пошаговый упрощенный пример.
У нас есть классы данных, которые представляют строки в БД.
trait Row
case class User(id: Int, age: Int, name: String) extends Row
case class SomeOtherData(id: Int, field1: List[String], field2: Double) extends Row
...
У нас есть API, который принимает любые json на маршрутах, таких как
PUT http://192.168.0.1/users/:userId
PUT http://192.168.0.1/other/:otherId
...
Например, мы вызываем PUT http://192.168.0.1/users/:userId next json
{ "age": 100 }
У нас есть специальный класс для декодирования json в
UpdateRequest[T <: Row](updates: List[Update[T]])
, где "updates" собираетсябыть похожим на
List(
Update((_: User).age, 100)
)
Полный пример с этим подходом вы можете найти на https://www.missingfaktor.me/writing/2018/08/12/composable-table-updates-in-slick/
Но, опять же, не имеет значения, каким он будет в концегонка, потому что причина проблемы в другом.
Итак, мы анализируем входящий json как UpdateRequest [User].1) Мы просматриваем все поля в Json и пытаемся найти каждое из них в LabelledGeneric [User] 2) Если поле найдено, мы пытаемся декодировать его до типа найденного с помощью circe.В противном случае DecodingFailure.
Это может быть похоже (типы и реализация не верны, просто пример, чтобы показать идею)
object UpdateRequest {
import shapeless._
import shapeless.ops.record._
def decode[T, U](s: (Symbol with Tagged[U], T), c: HCursor)(implicit decoder: Decoder[T]) = {
c.downField(s._1.name).as[T]
}
implicit def decoder[R <: Row, HL <: HList]()(implicit gen: LabelledGeneric.Aux[R, HL]): Decoder[UpdateRequest[R]] = new Decoder[UpdateRequest[R]] {
final def apply(c: HCursor): Decoder.Result[UpdateRequest[R]] = {
c.keys match {
case Some(keys) =>
// we got "age" key from json
// for each json key we try to find field in LabelledGeneric's Repr
// (maybe we need Fields here instead)
// so we found "age" in case class User and determine the type is Int
// and then try to decode to Int
val field = ... //found field from Repr
for {
age <- decode(field, c)
} yield ...
// and after make it as UpdateRequest[Row] (not needed to implement, the problem is above)
case None => Left(DecodingFailure("Empty json", Nil))
}
}
}
}
Спасибо всем заранее.