14

I think this will be better explained with an example

I have the following case class

case class Person(name: String = "no name", surname: String = "no surname")

And I want to make a general function to populate it from, for example, a json message, that might not specify all fields

I know that to use the default values the simple answer is not to pass them to the constructor, but if I have several fields that may or may not appear in the json, I should have to use a huge switch sentence covering every possible combination of missing parameters. In this case, after reading the json, I should take care of name & surname present, no name, no surname and no name nor surname case... (Gee, I hope I made myself clear).

To be more precise, I'm trying to develop a function that allows me to create a person from the following json values, using the default values when there's some parameter missing

{ "name": "john", "surname": "doe" }
{ "surname": "doe" }
{ "name": "john" }
{ }

That's why I'm looking for a more general way to handle this.

(I'll show some pseudo code to give and idea of what I'm trying to achieve)

I was thinking about something like:

val p = Person(name= "new person name", surname= Unit)

And in that case surname should get the default value

Or something like

val p = Person( Map( "name" -> "new person name" ) _* )

So that it also takes the default value for surname

Or maybe doing it in the constructor, if I detect a null value (or None) I could assign the default value.

In fact, I'm trying to avoid repeating the definition of the default values.

Anyway, what would be the most idiomatic way to achieve such a thing?

2 Answers 2

24

If you want the default value to be used, you normally just leave off that named argument:

scala> val p = Person(name = "new person name")
p: Person = Person(new person name,no surname)

But, since you want to explicitly know whether a value should be defaulted or not, you could implement your Map-based idea in a constructor. If you don't want to repeat the defaults, how about these two options:

Option 1: Externalized constants for defaults

Set the defaults externally. Use them in both the main constructor and the Map-based constructor.

val nameDefault = "no name"
val surnameDefault = "no surname"

case class Person(name: String = nameDefault, surname: String = surnameDefault) {
  def this(m: Map[String, String]) =
    this(m.getOrElse("name", nameDefault), m.getOrElse("surname", surnameDefault))
}

Usage:

new Person(name = "new person name", surname = "new person surname")
new Person(Map("name" -> "new person name"))
new Person(name = "new person name")

Option 2: Optionified alternate constructor

You may find this a little cleaner since it doesn't rely on externalized constants. The only downside here is that if you want to construct with only some of the parameters, you have to wrap each one in Some().

case class Person(name: String, surname: String) {
  def this(name: Option[String] = None, surname: Option[String] = None) =
    this(name.getOrElse("no name"), surname.getOrElse("no surname"))

  def this(m: Map[String, String]) = this(m.get("name"), m.get("surname"))
}

Usage:

new Person(name = "new person name", surname = "new person surname")
new Person(Map("name" -> "new person name"))
new Person(name = Some("new person name"))
new Person(name = "new person name") // can't do this
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks dhg, I understand that, but I need a generic function to load values that may not be there, I edited the question to clarify the problem
@opensas, I see now. I've updated with two options that might be what you're looking for.
great answer, I think I prefer the first approach, setting the constants in the Person companion object, just to make them a little less external...
+1 For option #1. I think that option #2 is Option abuse, though.
+1 for option #1 as well, creative use of alt constructor with Map param. json or servlet params a great fit here, thanks
0

I think this might be a usecase for Either. You can specify an Either with two types.

val name: Eiter[String, Unit] = Left("name")
val surname: Either[String, Unit] = Right( () )

Now you can check whether you have a Left or a Right and call the constructor with the arguments you want.

1 Comment

I'm trying to avoid having to call the constructor with every possible present/missing parameter combination... I don't know if I followed you...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.