代码之家  ›  专栏  ›  技术社区  ›  Tim Gilbert

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

  •  9
  • Tim Gilbert  · 技术社区  · 17 年前

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

    所有这些在Scala中都不难做到,但我不清楚的是如何最好地表达 data

    基本上,这个参数应该能够包含JSON中的各种内容:一些原语(字符串、int、布尔、null),或者零个或多个项的列表,或者零个或多个项的映射。(出于这个问题的目的,可以将贴图限制为具有字符串键,这似乎是Scala喜欢的方式。)

    我最初的想法是使用 Map[string, Any] Map<String, ?> ,我知道Scala的一位作者设计了Java的泛型。

    我特别好奇的一件事是,具有类似类型系统的其他函数式语言如何处理此类问题。我有一种感觉,我在这里真正想做的是提出一组案例类,我可以对其进行模式匹配,但我不太能够想象这会是什么样子。

    我有 Scala编程 ,但老实说,我的眼睛开始盯着协变/逆变的东西,我希望有人能更清楚、简洁地向我解释这一点。

    3 回复  |  直到 17 年前
        1
  •  15
  •   Jorge Ortiz    17 年前

    您需要一些case类来为您的数据类型建模。在函数式语言中,这些类型的东西被称为“抽象数据类型”,你可以通过谷歌搜索一下Haskell是如何使用它们的。Scala相当于Haskell的ADT使用了密封的traits和case类。

    让我们看一个例子 rewrite of the JSON parser combinator JsValue JsValue 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("{",", ","}")
      }
    

    模式匹配使我们能够确保处理每一种情况,还可以从JsType中“展开”底层值。它提供了一种类型安全的方式来知道我们已经处理了每一个案例。

    此外,如果您在编译时知道正在处理的JSON数据的结构,您可以做一些非常酷的事情,比如 n8han's extractors . 非常强大的东西,看看吧。

        2
  •  1
  •   Daniel Spiewak    17 年前

    嗯,有两种方法可以解决这个问题。我可能会用 Map[String, Any] collection.immutable 而不是 collection.mutable ).然而,如果你 想要经历一些痛苦,可以为此提供一种类型:

    sealed trait InnerData[+A] {
      val value: A
    }
    
    case class InnerString(value: String) extends InnerData[String]
    case class InnerMap[A, +B](value: Map[A, B]) extends InnerData[Map[A, B]]
    case class InnerBoolean(value: Boolean) extends InnerData[Boolean]
    

    现在,假设您正在阅读JSON data jsData ,您将为该字段指定以下类型:

    val jsData: Map[String, Either[Int, InnerData[_]]
    

    每次你把一块地从 ,则需要进行模式匹配,检查值是否为类型 Left[Int] Right[InnerData[_]] Either[Int, InnerData[_]] ).一旦你有了内部数据,你就可以进行模式匹配了 那个 确定它是否表示 InnerString , InnerMap InnerBoolean

    从技术上讲,您必须进行这种模式匹配,以便在从JSON中提取数据后使用它。好类型方法的优点是编译器会检查您,以确保您没有错过任何可能性。缺点是你不能跳过不可能的事情(比如 'eggs' 映射到 Int

    type DataType[A] = Map[String, Either[Int, InnerData[A]]]
    
    val jsData: DataType[_]
    

        3
  •  1
  •   starblue    17 年前

    JSON被用作“Scala编程”中的一个例子,在关于combinator解析的一章中。

    推荐文章