我应该如何在Scala中指定类似JSON的非结构化数据的类型?

Tim*_*ert 9 generics json scala strong-typing data-structures

我正在考虑将一个非常简单的文本模板库移植到scala,主要是作为学习语言的练习.该库目前在Python和Javascript中实现,其基本操作或多或少归结为此(在python中):

template = CompiledTemplate('Text {spam} blah {eggs[1]}')
data = { 'spam': 1, 'eggs': [ 'first', 'second', { 'key': 'value' }, true ] }
output = template.render(data)
Run Code Online (Sandbox Code Playgroud)

在Scala中这一点都不是非常难以做到的,但我不清楚的是如何最好地表达data参数的静态类型.

基本上这个参数应该能够包含你在JSON中找到的各种东西:一些基元(字符串,整数,布尔值,空),零个或多个项目的列表,或者零个或多个项目的映射.(出于这个问题的目的,地图可以被限制为具有字符串键,这似乎是Scala喜欢的东西.)

我最初的想法只是使用a Map[string, Any]作为顶级对象,但这对我来说似乎并不完全正确.实际上我不想在那里添加任何类的任意对象; 我只想要上面提到的元素.与此同时,我认为在Java中我能得到的最接近的是Map<String, ?>,而且我知道Scala的一位作者设计了Java的泛型.

我特别好奇的一件事是具有相似类型系统的其他功能语言如何处理这类问题.我有一种感觉,我真正想要做的是提出一组我可以模式匹配的案例类,但我不太能想象它会是什么样子.

我有Scala编程,但说实话,我的眼睛开始对协方差/逆变器的东西上釉一点,我希望有人可以更清楚,更简洁地向我解释这一点.

Jor*_*tiz 14

您可以通过某种案例类来建模数据类型.在函数式语言中,这些东西被称为"抽象数据类型",您可以通过Google搜索来了解Haskell如何使用它们.Scala相当于Haskell的ADT使用密封特征和案例类.

让我们看看Scala标准库或Scala编程书中的JSON解析器组合器重写.它不使用Map [String,Any]来表示JSON对象,而是使用Any来表示任意JSON值,而是使用抽象数据类型JsValue来重新定义JSON值.JsValue具有几个亚型,表示可能种JSON值:JsString,JsNumber,JsObject,JsArray,JsBoolean(JsTrue,JsFalse),和JsNull.

操作此表单的JSON数据涉及模式匹配.由于JsValue是密封的,如果您没有处理所有情况,编译器会发出警告.例如,toJson一个获取JsValue和返回String该值表示的方法的代码如下所示:

  def toJson(x: JsValue): String = x match {
    case JsNull => "null"
    case JsBoolean(b) => b.toString
    case JsString(s) => "\"" + s + "\""
    case JsNumber(n) => n.toString
    case JsArray(xs) => xs.map(toJson).mkString("[",", ","]")
    case JsObject(m) => m.map{case (key, value) => toJson(key) + " : " + toJson(value)}.mkString("{",", ","}")
  }
Run Code Online (Sandbox Code Playgroud)

模式匹配既可以让我们确保我们处理每个案例,也可以从其JsType中"解开"底层值.它提供了一种类型安全的方式来了解我们已经处理过每一个案例.

此外,如果你在编译时知道你正在处理的JSON数据的结构,你可以像n8han的提取器那样做一些非常酷的东西.非常强大的东西,检查出来.