条件对象到映射的转换

huangapple go评论86阅读模式
英文:

Conditional object to Map conversion

问题

  1. case class Foo(a: String, b: Option[Int] = None, c: Option[String] = None)
  2. def toMap: Map[String, Any] = {
  3. Map("a" -> a, "b" -> b, "c" -> c).collect {
  4. case (key, Some(value)) => key -> value
  5. }
  6. }
  1. val foo = Foo("bar")
  2. val extFoo = Foo("ext", b = Some(12))
  3. val fooMap = foo.toMap
  4. println(fooMap)
  5. // Map(a -> bar)
  6. val extMap = extFoo.toMap
  7. println(extMap)
  8. // Map(a -> ext, b -> 12)

希望这个翻译对您有所帮助。如果您有任何其他问题,请随时提出。

英文:

I have a Scala case class, where some fields are Option. Like this:

  1. case class Foo(a: String, b: Option[Int] = None, c: Option[String] = None)

I would like to convert this object to a Map[String, Any].

I have the following method:

  1. def toMap: Map[String, Any] = {
  2. Map("a" -> a, "b" -> b, "c" -> c).collect {
  3. case (key, Some(value)) => key -> value
  4. }

However, this method returns empty Map.

How should it be done?

//EDIT

Here's the example object of the Foo class:

  1. val foo = Foo("bar")
  2. val extFoo = Foo("ext", b = Some(12))

when calling toMap on this object, I want receive the following:

  1. val fooMap = foo.toMap
  2. println(fooMap)
  3. // Map(a -> bar)
  4. val extMap = extFoo.toMap
  5. println(extMap)
  6. // Map(a -> bar, b -> 12)

Basically, I want to remove key-value pairs, if the Option is equal to None, but still be able to map required fields.

答案1

得分: 1

为了解决您的问题,您需要在您正在应用于collect方法中的模式匹配中添加另一个case,以捕获其他情况。

  1. case class Foo(a: String, b: Option[Int] = None, c: Option[String] = None) {
  2. def toMapCollect: Map[String, Any] =
  3. Map("a" -> a, "b" -> b, "c" -> c).collect {
  4. case (key, value) if !value.isInstanceOf[None.type] => key -> value
  5. }
  6. def toMapFilter: Map[String, Any] =
  7. Map("a" -> a, "b" -> b, "c" -> c).filter { keyValue =>
  8. val (_, value) = keyValue
  9. !value.isInstanceOf[None.type]
  10. }
  11. def toMapFilterNot: Map[String, Any] =
  12. Map("a" -> a, "b" -> b, "c" -> c).filterNot { keyValue =>
  13. val (_, value) = keyValue
  14. value.isInstanceOf[None.type]
  15. }
  16. // 还有一种选项是手动构建Map,将`Option`放入`List[(String,Option[_])]`中,并检查它们是否不为空
  17. def anotherToMap: Map[String,Any] = {
  18. Map("a" -> a) ++ List(("b" -> b), ("c" -> c)).filter(_._2.nonEmpty)
  19. }
  20. }

collect方法类似于应用过滤器然后应用map的操作。

根据提供的代码,只有在bc字段均为None时,toMap才会返回空Map。如果我写了以下代码:

  1. val foo1 = Foo("1", None, None)
  2. val foo2 = Foo("2", Some(2), None)
  3. val foo3 = Foo("3", None, Some("3"))
  4. val foo4 = Foo("4", Some(4), Some("4"))
  5. println(s"foo1: ${foo1.toMap}")
  6. println(s"foo2: ${foo2.toMap}")
  7. println(s"foo3: ${foo3.toMap}")
  8. println(s"foo4: ${foo4.toMap}")

输出将是:

  1. foo1: Map()
  2. foo2: Map(b -> 2)
  3. foo3: Map(c -> 3)
  4. foo4: Map(b -> 4, c -> 4)

我们可以观察到以下内容:

  • a始终被丢弃
  • 只有当它们为Some时,bc才会被保留

如果查看Map.collect的文档,我们可以看到scaladoc和该函数的签名如下:

  1. /** 通过将部分函数应用于此`map`的所有定义域上的所有元素来构建新集合。
  2. *
  3. * @param pf 过滤和映射`map`的部分函数。
  4. * @tparam K2 返回的`map`的键类型。
  5. * @tparam V2 返回的`map`的值类型。
  6. * @return 通过将给定的部分函数`pf`应用于其定义域上的每个元素并收集结果而得到的新`map`。
  7. * 保留元素的顺序。
  8. */
  9. def collect[K2, V2](pf: PartialFunction[(K, V), (K2, V2)]): Map[K2, V2]

这意味着collect接收一个名为pf的参数,其类型为PartialFunction函数具有其定义域,在定义域内定义了一个部分函数,它也是一个函数,但其定义域是一个子域(可以是完整的定义域、只是子集,也可以为空)。

对于函数平方根,其定义域(仅为实数)为0和正数。在Scala中,您可以像这样定义平方根的PartialFunction

  1. val squareRoot: PartialFunction[Double, Double] = {
  2. case x if x >= 0 => Math.sqrt(x)
  3. }

PartialFunction提供了isDefinedAt函数,可以让您知道函数是否在特定值上定义。

您所做的每个case x if condition => value都是在应用名为模式匹配的东西。当您编写以下代码时:

  1. case (key, Some(value)) => key -> value

您在告诉系统只考虑key的任何String以及valueSome[Any]用于Map。

回到您定义的函数:

  1. case class Foo(a: String, b: Option[Int] = None, c: Option[String] = None) {
  2. def toMap: Map[String, Any] =
  3. Map("a" -> a, "b" -> b, "c" -> c).collect {
  4. case (key, Some(value)) => key -> value
  5. }
  6. }

我们可以像这样编写相同的代码:

  1. case class Foo(a: String, b: Option[Int] = None, c: Option[String] = None) {
  2. val partialFunction: PartialFunction[(String,Any),(String,Any)] = {
  3. case (key, Some(value)) => key -> value
  4. }
  5. def toMap: Map[String, Any] =
  6. Map("a" -> a, "b" -> b, "c" -> c).collect(partialFunction)
  7. }

然后,我们可以询问partialFunction是否在不同的值上定义。

  1. // 基于PartialFunction被类型化为[(String,Any),(String,Any)],
  2. // 我们只能传递类型为(String, Any)的元组给`isDefinedAt`
  3. // 这些将返回true
  4. partialFunction.isDefinedAt(("someInt",Some(1234)))
  5. partialFunction.isDefinedAt(("someString",Some("some string")))
  6. partialFunction.isDefinedAt(("someBoolean",Some(false)))
  7. // 这些将返回false
  8. partialFunction.isDefinedAt(("
  9. <details>
  10. <summary>英文:</summary>
  11. **Edit: TL;DR**
  12. to solve the issue you have, you would need to add another `case` to the [pattern matching](https://docs.scala-lang.org/tour/pattern-matching.html) you are applying in the `collect` method where you catch the rest of the cases.
  13. ```scala
  14. case class Foo(a: String, b: Option[Int] = None, c: Option[String] = None) {
  15. def toMapCollect: Map[String, Any] =
  16. Map(&quot;a&quot; -&gt; a, &quot;b&quot; -&gt; b, &quot;c&quot; -&gt; c).collect {
  17. case (key, value) if !value.isInstanceOf[None.type] =&gt; key -&gt; value
  18. }
  19. def toMapFilter: Map[String, Any] =
  20. Map(&quot;a&quot; -&gt; a, &quot;b&quot; -&gt; b, &quot;c&quot; -&gt; c).filter { keyValue =&gt;
  21. val (_, value) = keyValue
  22. !value.isInstanceOf[None.type]
  23. }
  24. def toMapFilterNot: Map[String, Any] =
  25. Map(&quot;a&quot; -&gt; a, &quot;b&quot; -&gt; b, &quot;c&quot; -&gt; c).filterNot { keyValue =&gt;
  26. val (_, value) = keyValue
  27. value.isInstanceOf[None.type]
  28. }
  29. // one more option is to build the Map manually, put the `Option`s in a `List[(String,Option[_])]` and check if they are not `None`
  30. def anotherToMap: Map[String,Any] = {
  31. Map(&quot;a&quot; -&gt; a) ++ List((&quot;b&quot; -&gt; b), (&quot;c&quot; -&gt; c)).filter(_._2.nonEmpty)
  32. }
  33. }

The collect method is similar to apply a filter and then a map.

Not sure exactly what you are trying to solve, but this doesn't look like a good approach. Using Any could force you to make type casting later.


Based on the provided code, the only case where toMap returns an empty Map, is when b and c fields are None. If I wrote the following

  1. val foo1 = Foo(&quot;1&quot;, None, None)
  2. val foo2 = Foo(&quot;2&quot;, Some(2), None)
  3. val foo3 = Foo(&quot;3&quot;, None, Some(&quot;3&quot;))
  4. val foo4 = Foo(&quot;4&quot;, Some(4), Some(&quot;4&quot;))
  5. println(s&quot;foo1: ${foo1.toMap}&quot;)
  6. println(s&quot;foo2: ${foo2.toMap}&quot;)
  7. println(s&quot;foo3: ${foo3.toMap}&quot;)
  8. println(s&quot;foo4: ${foo4.toMap}&quot;)

the output will be

  1. foo1: Map()
  2. foo2: Map(b -&gt; 2)
  3. foo3: Map(c -&gt; 3)
  4. foo4: Map(b -&gt; 4, c -&gt; 4)

we can observe the following:

  • a is always discarded
  • b and c are only kept when they are Some

If we check the documentation of Map.collect, we can see that the scaladoc and the signature of that function is the following

  1. /** Builds a new collection by applying a partial function to all elements of this `map`
  2. * on which the function is defined.
  3. *
  4. * @param pf the partial function which filters and maps the `map`.
  5. * @tparam K2 the key type of the returned `map`.
  6. * @tparam V2 the value type of the returned `map`.
  7. * @return a new `map` resulting from applying the given partial function
  8. * `pf` to each element on which it is defined and collecting the results.
  9. * The order of the elements is preserved.
  10. */
  11. def collect[K2, V2](pf: PartialFunction[(K, V), (K2, V2)]): Map[K2, V2]

This means that collect receives a parameter named pf of type PartialFunction. A function has a domain where is defined, a partial function is also a function but the domain is a subdomain (it could the complete domain, just a subset or it could be empty).


If we consider the function square root, the domain (only real numbers) is 0 and positive numbers. In scala, you can define a PartialFunction for square root like this

  1. val squareRoot: PartialFunction[Double, Double] = {
  2. case x if x &gt;= 0 =&gt; Math.sqrt(x)
  3. }

The trait PartialFunction provides the function isDefinedAt that lets you know if the function is defined at an specific value.


What you are doing for each case x if condition =&gt; value you are applying something named Pattern Matching. When you write

  1. case (key, Some(value)) =&gt; key -&gt; value

you are saying that you will only consider any String for the key and only Some[Any] for the value of the Map.


Backing to the function you defined

  1. case class Foo(a: String, b: Option[Int] = None, c: Option[String] = None) {
  2. def toMap: Map[String, Any] =
  3. Map(&quot;a&quot; -&gt; a, &quot;b&quot; -&gt; b, &quot;c&quot; -&gt; c).collect {
  4. case (key, Some(value)) =&gt; key -&gt; value
  5. }
  6. }

we could write the same code like this

  1. case class Foo(a: String, b: Option[Int] = None, c: Option[String] = None) {
  2. val partialFunction: PartialFunction[(String,Any),(String,Any)] = {
  3. case (key, Some(value)) =&gt; key -&gt; value
  4. }
  5. def toMap: Map[String, Any] =
  6. Map(&quot;a&quot; -&gt; a, &quot;b&quot; -&gt; b, &quot;c&quot; -&gt; c).collect(partialFunction)
  7. }

Then we could ask to the partialFunction if it is defined at different values.

  1. // based that the PartialFunction is typed as [(String,Any),(String,Any)],
  2. // we can only pass a tuple of type (String, Any) to `isDefinedAt`
  3. // these will return true
  4. partialFunction.isDefinedAt((&quot;someInt&quot;,Some(1234)))
  5. partialFunction.isDefinedAt((&quot;someString&quot;,Some(&quot;some string&quot;)))
  6. partialFunction.isDefinedAt((&quot;someBoolean&quot;,Some(false)))
  7. // these will return false
  8. partialFunction.isDefinedAt((&quot;none&quot;,None))
  9. partialFunction.isDefinedAt((&quot;string&quot;,&quot;string&quot;))
  10. partialFunction.isDefinedAt((&quot;boolean&quot;,true))
  11. partialFunction.isDefinedAt((&quot;int&quot;,5))
  12. partialFunction.isDefinedAt((&quot;double&quot;,1.5))

huangapple
  • 本文由 发表于 2023年5月24日 20:22:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/76323494.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定