ホーム>source

ケースクラスのペア Source が与えられた場合  および Target 、ネストされたケースクラスを持ち、ネストの各レベルで Target のフィールド   Source のサブセットのアライメントされていないサブセットです 、 Source から一般的なShapelessを使用した変換を記述する方法はありますか   Target へ ?

たとえば、次の Internal が与えられた場合  および External  クラス:

object Internal {
  case class User(
    firstName: String,
    lastName: String,
    isAdmin: Boolean,
    address: Address
  )
  case class Address(
    street: String,
    country: String,
    blacklisted: Boolean
  )
}
object External {
  // Note that isAdmin is missing and the fields are jumbled
  case class User(
    lastName: String,
    firstName: String,
    address: Address
  )
  // blacklisted is gone
  case class Address(
    street: String,
    country: String
  )
}

次のようなことができるようになりたい

val internalUser = Internal.User(
  firstName = "Joe",
  lastName = "Blow",
  isAdmin = false,
  address = Internal.Address(
    street = "Sesame",
    country = "U-S-A",
    blacklisted = false
  )
)
val externalUser = Transform.into[External.User](internalUser)

私が持っていますいくつかのコード これはサブセットの選択とフィールドの整列を処理しますが、再帰部分はもう少し難しいです:

import shapeless._, ops.hlist.Align, ops.hlist.SelectAll, SelectAll._
class Transform[T] {
  // The fun stuff. Given an S, returns a T, if S has the right (subset of) fields
  def apply[S, SR <: HList, TR <: HList](s: S)(
      implicit
      genS: LabelledGeneric.Aux[S, SR],
      genT: LabelledGeneric.Aux[T, TR],
      selectAll: SelectAll[SR, TR],
      align: Align[SelectAll[SR, TR]#Out, TR]): T =
    genT.from(align(selectAll(genS.to(s))))
}
object Transform {
  // Convenience method for building an instance of `Transform`
  def into[T] = new Transform[T]
}

私は見てきたこのSO質問、しかし、答えは、フィールドが他のフィールドの非整列サブセットであるという事実を考慮していません。

あなたの答え
  • 解決した方法 # 1

    これは、結果を得るためにさまざまなプリミティブを形を整えずにつなぎ合わせることの楽しい練習でした。以下は、Scala 2.12.6および2.13.0-M5のshapeless 2.3.3でテストされています...

    Transform を定義できます  型クラスはそうです、

    import shapeless._, ops.hlist.ZipWithKeys, ops.record.{ Keys, SelectAll, Values }
    trait Transform[T, U] {
      def apply(t: T): U
    }
    object Transform {
      def into[U] = new MkTransform[U]
      class MkTransform[U] {
        def apply[T](t: T)(implicit tt: Transform[T, U]): U = tt(t)
      }
      // The identity transform
      implicit def transformId[T]: Transform[T, T] =
        new Transform[T, T] {
          def apply(t: T): T = t
        }
      // Transform for HLists
      implicit def transformHCons[H1, T1 <: HList, H2, T2 <: HList]
        (implicit
          th: Transform[H1, H2],
          tt: Transform[T1, T2]
        ): Transform[H1 :: T1, H2 :: T2] =
        new Transform[H1 :: T1, H2 :: T2] {
          def apply(r: H1 :: T1): H2 :: T2 = th(r.head) :: tt(r.tail)
        }
      // Transform for types which have a LabelledGeneric representation as
      // a shapeless record
      implicit def transformGen
        [T, U, TR <: HList, UR <: HList, UK <: HList, UV <: HList, TS <: HList]
        (implicit
          genT:    LabelledGeneric.Aux[T, TR],  // T <-> corresponding record
          genU:    LabelledGeneric.Aux[U, UR],  // U <-> corresponding record
          keysU:   Keys.Aux[UR, UK],            // Keys of the record for U
          valuesU: Values.Aux[UR, UV],          // Values of the record for U
          selT:    SelectAll.Aux[TR, UK, TS],   // Select the values of the record of T
                                                //   corresponding to the keys of U
          trans:   Lazy[Transform[TS, UV]],     // Transform the selected values
          zipKeys: ZipWithKeys.Aux[UK, UV, UR], // Construct a new record of U from the
                                                //   transformed values
        ): Transform[T, U] =
        new Transform[T, U] {
          def apply(t: T): U = {
            genU.from(zipKeys(trans.value(selT(genT.to(t)))))
          }
        }
    }
    
    

    興味深いケースは transformGen です 。型変数 T  および U  ソースとターゲットのタイプであり、コールサイトで固定されています。暗黙の引数は上から下に解決されるため、残りの型変数は左から右に順番に解決されます...ほとんどの場合、各暗黙の最後の型引数は先行する型引数が与えられると解決され、解決策は右/次の解像度まで。

    shapelessの Lazy の使用にも注意してください  再帰的暗黙引数 trans の保護 。これは例では厳密に必要というわけではありませんが、より複雑な場合や再帰的な場合があります。また、Scala 2.13.0-M5以降では trans  代わりに、名前による暗黙的な引数として定義できます。

    今、あなたの定義を与えられ、

    val internalUser = Internal.User(
      firstName = "Joe",
      lastName = "Blow",
      isAdmin = false,
      address = Internal.Address(
        street = "Sesame",
        country = "U-S-A",
        blacklisted = false
      )
    )
    
    

    次のように動作します

    val expectedExternalUser = External.User(
      lastName = "Blow",
      firstName = "Joe",
      address = External.Address(
        street = "Sesame",
        country = "U-S-A",
      )
    )
    val externalUser = Transform.into[External.User](internalUser)
    assert(externalUser == expectedExternalUser)
    
    

  • 前へ java - JPAクエリ:サブクエリをグループ化条件に結合する
  • 次へ java - APIへのHTTPポストが403 FORBIDDENを返します