案例类-使用转换复制多个字段

时间:2019-05-27 05:47:36

标签: scala generics shapeless case-class

我有一个包含25个字段的case类,需要将其转换为22个字段,其中19个是共享的,而3个只是简单地重命名。

我找到了一些使用shapeless进行操作的示例(例如an answer here,以及Miles Sabin herehere的一些代码示例),但最后一个是这些看起来有些过时了,我无法从Github示例中弄清楚如何使用shapeless重命名多个字段,或者如何在将字段添加到新对象之前对字段进行更多操作。有人可以帮我吗?

简化的代码示例;

import shapeless.LabelledGeneric
case class A(fieldA:Int, fieldB:String, fieldC:String)
case class B(fieldARenamed:Int, fieldB:String, fieldC:String, fieldCTransformed:String)

val aGen = LabelledGeneric[A]
val bGen = LabelledGeneric[B]

val freddie = new A(1,"Freddie","somestring")

val record = aGen.to(freddie)
val atmp = freddie.fieldA
record.Remove("fielda")

val freddieB = bGen.from(record + 
  (Symbol("fieldARenamed") ->> atmp) +
  (Symbol("fieldCTransformed") ->> freddie.fieldC.toUpperCase)
) //Errors everywhere, even if I replace + with :: etc.

我有一种Align的感觉会出现在这里的某处,但了解如何以最精简的方式做到这一点-例如而不会像上面的第三个链接那样创建Field之类的其他特征,将会很有趣。

The Shapeless Guide中,还使用了单引号(例如'fieldC)表示法,但我在其中找不到很多信息,因此如果这起了一定作用,解释也将非常有帮助。对于这种Scala巫术的深度来说,这还算是新事物,因此,如果这个问题显得晦涩难懂或涵盖了太多不同的话题,就此道歉。

编辑:为免生疑问,我不是寻找答案,这些建议表明我只是通过引用第一个字段来手动创建新的案例类,例如;

val freddieB = B(fieldARenamed = freddie.fieldA, fieldB = freddie.fieldB, fieldC = freddie.fieldC, fieldCTransformed =freddie.fieldC.toUpperCase)

出于各种原因,请参阅以下评论。

3 个答案:

答案 0 :(得分:2)

最简单的解决方案是使用旧case class中的值构造新import os import pysftp import socket from stat import S_IMODE, S_ISDIR, S_ISREG cnopts = pysftp.CnOpts() cnopts.hostkeys = None IP = "192.168.X.X" myUsername = "user" port = 22 myPassword = "password" try: with pysftp.Connection(host=IP, username=myUsername, password=myPassword, cnopts=cnopts) as sftp: try: r=str(socket.gethostbyaddr(IP)) print("connection successful with "+r) def get_r_portable(sftp, remotedir, localdir, preserve_mtime=False): for entry in sftp.listdir(remotedir): remotepath = remotedir + "/" + entry localpath = os.path.join(localdir, entry) mode = sftp.stat(remotepath).st_mode if S_ISDIR(mode): try: os.mkdir(localpath, mode=777) except OSError: pass get_r_portable(sftp, remotepath, localpath, preserve_mtime) elif S_ISREG(mode): sftp.get(remotepath, localpath, preserve_mtime=preserve_mtime) remote_path = input("enter the remote_path: ") local_path = input("enter the local_path: ") get_r_portable(sftp, remote_path, local_path, preserve_mtime=False) except socket.herror: print("Unknown host") except: print("connection failed") 的实例,并根据需要将函数应用于这些值。该代码将非常高效,代码的目的将非常明确,比任何其他解决方案花费的时间更少,比依赖第三方库的解决方案更健壮和可维护,并且避免了这两个类之间存在隐藏的依赖关系。

答案 1 :(得分:2)

另一个选择是使用automapper;特别是Dynamic Mappings功能。

对于您的特定示例,其外观如下:

import io.bfil.automapper._

case class A(fieldA:Int, fieldB:String, fieldC:String)
case class B(fieldARenamed:Int, fieldB:String, fieldC:String, fieldCTransformed:String)

val freddie = new A(1,"Freddie","somestring")

val freddieB = automap(freddie).dynamicallyTo[B](
  fieldARenamed = freddie.fieldA, 
  fieldCTransformed = freddie.fieldC.toUpperCase
)

我想你可以使其功能

def atob(a: A): B = {
  automap(a).dynamicallyTo[B](
    fieldARenamed = a.fieldA, 
    fieldCTransformed = a.fieldC.toUpperCase
  )
}

从效率的角度来看,此lib使用宏,因此generated code实际上和手工编写的一样好

答案 2 :(得分:0)

仅供参考,这是一种使问题代码正常工作的方法。

import shapeless._
import shapeless.labelled.FieldType
import shapeless.ops.hlist.{Align,Intersection}
import shapeless.syntax.singleton._

case class A(fieldA:Int, fieldB:String, fieldC:String)
case class B(fieldARenamed:Int, fieldB:String, fieldC:String, fieldCTransformed:String)

val fromGen = LabelledGeneric[A]
val toGen   = LabelledGeneric[B]

val freddie = A(1, "Freddie", "somestring")
val putARename = Symbol("fieldARenamed")     ->> freddie.fieldA
val putCTrans  = Symbol("fieldCTransformed") ->> freddie.fieldC.toUpperCase

trait Field { type K; type V; type F = FieldType[K, V] }
object Field {
  def apply[K0,V0](sample: FieldType[K0,V0]) =
    new Field { type K = K0; type V = V0 }
}

val pFieldA  = Field(putARename)
val pFieldCT = Field(putCTrans)

val inter = Intersection[pFieldA.F :: pFieldCT.F :: fromGen.Repr, toGen.Repr]
val align = Align[inter.Out, toGen.Repr]

toGen.from(align(inter(putARename :: putCTrans :: fromGen.to(freddie))))
//res0: B = B(1,Freddie,somestring,SOMESTRING)