scalalensesmonocle-scala

Update deeply nested case class with Options


I have a 3-level nested case class model with a bunch of options that represents some data in a database. It's essentially:

case class User(settings: Option[Settings])
case class Settings(keys: Option[List[KeySet]])
case class KeySet(privateKey: String, publicKey: String)

I understand how to get deeply nested fields out of this using some for comprehension or chains of flatMap (Scala Option object inside another Option object) and I also understand how to update it using a lens library, but I want to figure out how to update the fields even if some stuff in the tree is None and automatically make Somes of those if they don't exist yet.

For example, how would I handle the case where I want to add to the keys List but the user hasn't yet set any settings? Is it possible to, in some sense, automatically create a Some(settings) field and also a Some(keys) field?

I have an idea of how to do it with a lot of pattern matching, but this seems wrong because of 1. rightward drift of code and 2. not using map or flatMap very much with the options.

Is this possible using a lens library on its own? I read here that it might not be possible: https://github.com/julien-truffaut/Monocle/issues/215 as in the case of Monocle it can't update an Option that is a None. Maybe I need to think about the problem another way?

Thanks


Solution

  • I'm not sure why you use Option[List[KeySet]]. Is there an important distinction between None and an empty List?

    In any case, I find fold to be a handy tool when working with Options.

    def updateUser(u :User, ks :KeySet) :User = {
      u.copy(settings =
        Some(u.settings.fold(Settings(Some(ks::Nil))) (stngs =>
          stngs.copy(keys = Some(stngs.keys.fold(ks::Nil) (ks::_))))))
    }
    
    val pat = updateUser(User(None), KeySet("a","b"))
    //pat: User = User(Some(Settings(Some(List(KeySet(a,b))))))
    
    val upat = updateUser(pat, KeySet("c","d"))
    //upat: User = User(Some(Settings(Some(List(KeySet(c,d), KeySet(a,b))))))