Home > Net >  Scala 2.13: Case class with extendable variable attributes?
Scala 2.13: Case class with extendable variable attributes?

Time:11-13

I want to create a case class which can incorporate a record of string and another case class entity.

For example:

case class Student(
name: String
age: Int
)
case class Example(
[key:String]: Student
)

Now I want to use Example to add multiple attributes where attribute could have N number of elements however the type of all those attributes would remain Student. Here's an example:

Example(student1 = Student("name",12),student2=Student("name2",13))

Reason why I am using Case class is that I need to transform this into a JSON using UPickle library and so I wanted to know on the feasibility of achieving the same.

Please note that Example class not just contains [key:String]: Student attribute types but also somethings like:

case class Example(
[key:String]: Student,
_logOp: Option[Boolean] = false,
queryName: String,
...
)

The transformed result for case class:

case class Example(
_logOp: String,
variation: String,
[key:String]: FiltersCaseClass 
/* This line I have added to simplify and make my problem more understandable. Basically the case class would contain some properties like `_logOp` `variation` and then a lot of keys with their values as another case class `FilterCaseClass`
*/
)

should look something like this:

{"_logOp":"AND","variation": "en","ids": {"_logOp": "OR","_expressions": [{"value": "242424"},{"value": "242422"}]}}

where FilterCaseClass is:

case class FilterCaseClass(
_logOp: String,
_expressions: Seq[SingleValueFilter]
)

where SingleValueFilter is another case class containing values

Edit 1:

As per one of the answers by Dymtro:

case class Example(
  m: Map[String, Student],
  _logOp: Option[Boolean] = Some(false),
  queryName: String
)
object Example {
  implicit val rw: ReadWriter[Example] = macroRW
}

write(Example(
  Map("student1" -> Student("name",12), "student2" -> Student("name2",13)),
  Some(true),
  "abc"
))
//{"m":{"student1":{"name":"name","age":12},"student2":{"name":"name2","age":13}},"_logOp":[true],"queryName":"abc"}

The only difference I want here is:

{"student1":{"name":"name","age":12},"student2":{"name":"name2","age":13},"_logOp":[true],"queryName":"abc"}

The difference is that I want case class to be flexible to add key value pairs of Student class.

CodePudding user response:

You don't need a case class Example, in µPickle you can create json mixing manual construction and case-class construction

import upickle.default.{macroRW, ReadWriter, write} // "com.lihaoyi" %% "ujson" % "0.9.6"

case class Student(
  name: String,
  age: Int
)

object Student {
  implicit val rw: ReadWriter[Student] = macroRW
}

ujson.Obj("student1" -> write(Student("name",12)), "student2" -> write(Student("name2",13)))
//{"student1":"{\"name\":\"name\",\"age\":12}","student2":"{\"name\":\"name2\",\"age\":13}"}

If [key:String]: Student means Map[String, Student] then µPickle seems to support this out-of-the-box

case class Example(
  m: Map[String, Student],
  _logOp: Option[Boolean] = Some(false),
  queryName: String
)
object Example {
  implicit val rw: ReadWriter[Example] = macroRW
}

write(Example(
  Map("student1" -> Student("name",12), "student2" -> Student("name2",13)),
  Some(true),
  "abc"
))
//{"m":{"student1":{"name":"name","age":12},"student2":{"name":"name2","age":13}},"_logOp":[true],"queryName":"abc"}

It shouldn't be nested within m

You can achieve this with a custom codec (pickler)

import upickle.default.{ReadWriter, macroRW, readwriter, transform, write, read}
import scala.collection.mutable

case class Example(
  m: Map[String, Student],
  _logOp: Option[Boolean] = Some(false),
  queryName: String
)
object Example {
  implicit val rw: ReadWriter[Example] = {
    val standardExampleRW = macroRW[Example]
    readwriter[ujson.Value].bimap[Example](
      example => transform[Example](example)(standardExampleRW).to[ujson.Value] match {
        case ujson.Obj(standardMap) =>
          val newMap = mutable.LinkedHashMap.empty[String, ujson.Value]
          standardMap.remove("m")
          newMap.addAll(example.m.map { case (str, stud) => str -> transform[Student](stud).to[ujson.Value]})
            .addAll(standardMap)
          ujson.Obj(newMap)
      },
      // if you don't need a reversed transform i.e. from a json to an Example then you can omit this part
      // _ => ??? 
      {
        case ujson.Obj(newMap) =>
          val logOpJson = newMap.remove("_logOp")
          val logOp = logOpJson.map(transform[ujson.Value](_).to[Option[Boolean]])
          val queryNameJson = newMap.remove("queryName")
          val queryName = queryNameJson.map(transform[ujson.Value](_).to[String]).getOrElse("")
          val m = newMap.map { case (str, json) => str -> transform[ujson.Value](json).to[Student] }.toMap
          logOp.map(Example(m, _, queryName)).getOrElse(Example(m, queryName = queryName))
      }
    )
  }
}

write(Example(
  Map("student1" -> Student("name",12), "student2" -> Student("name2",13)),
  Some(true),
  "abc"
))
//{"student1":{"name":"name","age":12},"student2":{"name":"name2","age":13},"_logOp":[true],"queryName":"abc"}

read[Example](
  """{"student1":{"name":"name","age":12},"student2":{"name":"name2","age":13},"_logOp":[true],"queryName":"abc"}"""
)
//Example(Map(student1 -> Student(name,12), student2 -> Student(name2,13)),Some(true),abc)

So, basically you can generate case classes in Scala but it's not necessary for serialization into json format.

  • Related