1

I have several objects that closely (but not perfectly) mirror other objects in Scala. For example, I have a PackagedPerson that has all of the same fields as the PersonModel object, plus some. (The PackagedPerson adds in several fields from other entities, things that are not on the PersonModel object).

Generally, the PackagedPerson is used for transmitting a "package" of person-related things over REST, or receiving changes back (again over REST).

When preparing these transactions, I have a pack method, such as:

def pack(p: PersonModel): PackagedPerson

After all the preamble is out of the way (for instance, loading optional, extra objects that will be included in the package), I create a PackagedPerson from the PersonModel and "everything else:"

new PackagedPerson(p.id, p.name, // these (and more) from the model object
                   x.profilePicture, y.etc // these from elsewhere
                  )

In many cases, the model object has quite a few fields. My question is, how can I minimize repetitive code.

In a way it's like unapply and apply except that there are "extra" parameters, so what I really want is something like this:

new PackagePerson(p.unapply(), x.profilePicture, y.etc)

But obviously that won't work. Any ideas? What other approaches have you taken for this? I very much want to keep my REST-compatible "transport objects" separate from the model objects. Sometimes this "packaging" is not necessary, but sometimes there is too much delta between what goes over the wire, and what gets stored in the database. Trying to use a single object for both gets messy fast.

Zaphod
  • 1,387
  • 2
  • 17
  • 33

2 Answers2

1

You could use LabelledGeneric from shapeless.

You can convert between a case class and its a generic representation.

case class Person(id: Int, name: String)
case class PackagedPerson(id: Int, name: String, age: Int)

def packagePerson(person: Person, age: Int) : PackagedPerson = {
    val personGen = LabelledGeneric[Person]
    val packPersonGen = LabelledGeneric[PackagedPerson]
    // turn a Person into a generic representation
    val rec = personGen.to(person)
    // add the age field to the record
    // and turn the updated record into a PackagedPerson
    packPersonGen.from(rec + ('age ->> age))
}

Probably the order of the fields of your two case classes won't correspond as nice as my simple example. If this is the case shapeless can reorder your fields using Align. Look at this brilliant answer on another question.

Community
  • 1
  • 1
Peter Neyens
  • 9,770
  • 27
  • 33
0

You can try Java/Scala reflection. Create a method that accepts a person model, all other models and model-free parameters:

def pack(p: PersonModel, others: Seq[Model], freeParams: (String, Any)*): PackedPerson

In the method, you reflectively obtain PackedPerson's constructor, see what arguments go there. Then you (reflectively) iterate over the fields of PersonModel, other models and free args: if there's a field the name and type of which are same as one of the cunstructor params, you save it. Then you invoke the PackedPerson constructor reflectively using saved args.

Keep in mind though, that a case class can contain only up to 22 constructor params.

Anatoliy Kmetyuk
  • 708
  • 1
  • 6
  • 10