Json 如何从scala case类修饰不可变对象图
我正在阅读结构化JSON,使用Play框架的JSON读取来构建带有案例类的对象图 例如:Json 如何从scala case类修饰不可变对象图,json,scala,playframework,immutability,case-class,Json,Scala,Playframework,Immutability,Case Class,我正在阅读结构化JSON,使用Play框架的JSON读取来构建带有案例类的对象图 例如: case class Foo ( id: Int, bar_id: Int, baz_id: Int, x: Int, y: String
case class Foo (
id: Int,
bar_id: Int,
baz_id: Int,
x: Int,
y: String
)
{
var bar: Bar = null
var baz: Baz = null
}
建造完Foo后,我必须稍后回来,通过设置酒吧和酒吧来装饰它。这些是在其他JSON文件中定义的,只有在所有解析完成时才知道。但这意味着Foo不可能是不变的
Scala中的“正确”方法是什么,在不多次重复Foo的每个字段的情况下,创建一个不可变的对象,然后创建它的装饰版本
我知道有几种感觉不对劲的方式:
- 生成“bar:Option[bar]”和“baz:Option[baz]”case类参数,然后我可以使用“copy”来创建Foo类的新版本,并将它们设置为某个值;但是,每次访问它们时,我都要检查它们——效率低下、不安全,无法制作一个保证结构正确的装饰性foo
- 创建第二个case类,它是第一个中所有结构的复制粘贴,但添加了两个额外的修饰参数-但这意味着在定义中回显整个参数列表,并在创建其实例时再次回显
- Case类继承显然是有争议的,而且在任何情况下似乎也要求我重复子类构造函数中的每个参数李>
- 创建一个非case超类,列出公共case类参数。然后在case类中扩展它。但这似乎仍然需要重复子类构造函数中的每个参数
- 我看到有人在博客中谈论这个问题,并在运行时使用反射来填充他们修饰副本的基本属性-这避免了echo,但现在没有类型安全性,将属性名称指定为字符串、开销等
Scala肯定有办法让人们从简单的不可变对象中组合出更复杂的不可变对象,而不必手动复制它们的每个部分?另一种策略可能是创建另一个case类:
case class Foo(
id: Int,
bar_id: Int,
baz_id: Int,
x: Int,
y: String
)
case class ProcessedFoo(
foo: Foo,
bar: Bar,
baz: Baz
)
您可以为处理的类型引入一个新特性、一个扩展该特性的类和一个隐式转换:
case class Foo(bar: Int)
trait HasBaz {
val baz: Int
}
class FooWithBaz(val foo: Foo, val baz: Int) extends HasBaz
object FooWithBaz {
implicit def innerFoo(fwb: FooWithBaz): Foo = fwb.foo
implicit class RichFoo(val foo: Foo) extends AnyVal {
def withBaz(baz: Int) = new FooWithBaz(foo, baz)
}
}
那么你可以做:
import FooWithBaz._
Foo(1).withBaz(5)
而且,尽管
withBaz
返回FooWithBaz
,但由于隐式转换,我们可以在必要时将返回值视为Foo
结合选项
和类型参数,您可以标记案例类,并静态跟踪处理的字段是否为空:
import scala.language.higherKinds
object Acme {
case class Foo[T[X] <: Option[X] forSome { type X }](a: Int,
b: String,
c: T[Boolean],
d: T[Double])
// Necessary, Foo[None] won't compile
type Unprocessed[_] = None.type
// Just an alias
type Processed[X] = Some[X]
}
我在当前项目中使用过一次。我遇到的主要问题是当我希望Foo
是协变的时
或者,如果您不关心
t
上的绑定:
case class Foo[+T[_]](a: Int, b: String, c: T[Boolean], d: T[Double])
然后,当您需要Foo[选项]
时,可以使用Foo[未处理]
或Foo[已处理]
scala> val foo: Foo[Option] = processed
foo: Acme.Foo[Option] = Foo(42,b,Some(true),Some(42.0))
这对我来说也是一个痛苦的时刻。在我看来,一般的问题是声明一个核心数据模型,然后以干巴巴的方式定义派生/扩充模型,这些模型是原始模型的转换。到目前为止,我还没有找到解决这个问题的通用方法。我明白了。也许这是所有邪恶中最小的一个,尽管它需要对每一个对foo的引用进行间接处理。我对这种方法很着迷。但是它没有编译,而且我担心,因为我还在努力解决您在这里要做的事情,所以我还不能解决它的问题。错误:(13,16)Play 2编译器:Foo.scala:13:RichFoo已经被定义为(编译器生成的)方法RichFoo隐式类RichFoo(val-Foo:Foo)扩展了AnyVal{^这个错误看起来非常奇怪而且毫无帮助-我还在学习隐式转换,如果解决方案明显,我道歉?隐式类必须在其他类或对象中。我想这可能是导致错误的原因。我在REPL中玩过这个,隐式转换的规则是sli完全不同。现在看看我的更改(我将
RichFoo
移动到FooWithBaz
对象中)。恐怕它仍然无法编译。我得到一个“错误:(16,20)value类可能不是另一个类的成员隐式类RichFoo(val-foo:foo)扩展AnyVal{^”Scala让我有点吃惊,这是可能的。不幸的是,我无法将其编译。未处理和已处理的类型语句因“预期的类或对象定义”而失败。我注意到,如果我将它们放在Foo{}的主体中它们可以编译,但您的示例用例不起作用,因为已处理/未处理的符号未知。@user2057354是的,您应该将类型声明放在对象中(而不是放在Foo类中)。然后您可以在需要时导入它们。例如,它可以在Foo的package的package对象中,也可以在Foo的companion对象中。我认为这太酷了。这确实可以让它编译和运行。但在我描述的场景中,它不适用于JSON读取。使用附加字段/参数会破坏读取(“缺少对象Foo中应用方法的参数”-因为我现在在Foo上有了JSON中没有的额外字段/属性)。即使我为c或d指定了一个默认的“None”值,情况也是如此,这似乎是错误的。例如隐式val fooreds:Reads[Foo[Unprocessed]=((\u\“a”).read[Int]和(\u\“b”).read[String])(Foo[未处理]…不起作用,可能无法工作?@user2057354使用Reads.pure
。类似于隐式val-fooreds:Reads[Foo[Unprocessed]]=(((uu\'a”).read[Int]和(uu\'b”).read[String]u\.read(Reads.pure(None))和u\.read(Reads.pure(None))(Foo[Unprocessed])/code>(可能需要一些额外的键入)。您还可以编写另一个应用方法:(a,b)=>Foo[未处理](a,b,None,None)
。
scala> val foo: Foo[Option] = processed
foo: Acme.Foo[Option] = Foo(42,b,Some(true),Some(42.0))