Бесформенный разбор Json LabelledGeneric - PullRequest
0 голосов
/ 15 декабря 2018

Я новичок в 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))
      }
    }
  }
}

Спасибо всем заранее.

1 Ответ

0 голосов
/ 16 декабря 2018

Это не совсем ваш случай, но кажется, что он очень близок к тому, что вам нужно.Вероятно, вы можете использовать эту идею.Я добавил несколько заглушек, чтобы код работал без каких-либо зависимостей.Идея заключалась в том, чтобы перебирать поля case вместо полей из json.Однако дизайн выглядит странно, так как мне пришлось создать экземпляр класса case, чтобы извлечь из него имена полей и типы без использования значений.

trait Decoder[T] {
  def apply(t:String): T
}

implicit val decoderStr: Decoder[String] =  (t) => t
implicit val decoderInt: Decoder[Int] =  (t) => t.toInt
implicit val decoderFloat: Decoder[Double] =  (t) =>  t.toDouble


val c = new {
  def downField(name:String):String = name match {
    case "x" => "5"
    case "y" => "some string"
    case "z" => "2.0"
  }
}

implicit class Stbb (a: String) {
  def as[T](implicit dc: Decoder[T])= dc.apply(a)
}

//end of stubs

import shapeless._
import shapeless.labelled._


case class Test(x: Int, y: String, z: Double)
val test = Test(1, "foo", 1.5)

val lg = LabelledGeneric[Test]
val genericTest = lg.to(test)


object mapToEncoded extends Poly1 {

  implicit def toEncodedElements[K,A](implicit key: Witness.Aux[K], dec: Decoder[A])  =
    at[FieldType[K,A]](_ => Some(c.downField(key.value.toString.drop(1)).as[A])))

}

//HList with Option[T] values decoded to proper type
val res = genericTest.map(mapToEncoded)
println(res)

Возможно, вам просто нужно заменить Optionс Decoder.Result и позаботиться об исключениях в toEncodedElements

...