如何在Scala中将地图,seq和基本类型的任何嵌套组合转换为JSON?

时间:2017-08-14 15:43:38

标签: json scala

在Python中,我可以任意嵌套列表和字典,并使用各种类型的混合,然后只需调用json.dumps(x)并获得我需要的结果。

我在Scala中找不到这样的东西。我遇到的所有库似乎都坚持静态类型和编译时检查。我宁愿为了简单而放弃。似乎应该可以动态检查输入的类型。

作为一个简单的例子,我希望能够做到这样的事情:

toJson(Map("count" -> 1,
           "objects" -> Seq(Map("bool_val" -> true,
                                "string_val" -> "hello"))))

将输出包含以下内容的字符串:

{"count": 1, "objects": [{"bool_val": true, "string_val": "hello"}]}

编辑:当我尝试几个库时会发生什么:

scala> import upickle.default._
import upickle.default._

scala> write(Seq(1, 2))
res0: String = [1,2]

scala> write(Seq(1, "2"))
<console>:15: error: Couldn't derive type Seq[Any]
       write(Seq(1, "2"))
            ^

scala> import spray.json._
import spray.json._

scala> import spray.json.DefaultJsonProtocol._
import spray.json.DefaultJsonProtocol._

scala> Seq(1, 2).toJson
res2: spray.json.JsValue = [1,2]

scala> Seq(1, "2").toJson
<console>:21: error: Cannot find JsonWriter or JsonFormat type class for Seq[Any]
       Seq(1, "2").toJson
                   ^

我尝试创建自己的喷雾通用协议但是我得到了奇怪的结果:

import spray.json._

object MyJsonProtocol extends DefaultJsonProtocol {

  implicit object AnyJsonWriter extends JsonFormat[Any] {
    def write(x: Any) = x match {
      case n: Int => JsNumber(n)
      case n: Long => JsNumber(n)
      case d: Double => JsNumber(d)
      case s: String => JsString(s)
      case b: Boolean if b => JsTrue
      case b: Boolean if !b => JsFalse
      case m: Map[Any, Any] => m.toJson
      case p: Product => p.productIterator.toList.toJson  // for tuples
      case s: Seq[Any] => s.toJson
      case a: Array[Any] => a.toJson
    }

    override def read(json: JsValue) = ???
  }

}

import MyJsonProtocol._

val objects = Seq(Map(
  "bool_val" -> true,
  "string_val" -> "hello"))

objects.toJson.toString
// [{"bool_val":true,"string_val":"hello"}]

Map(
  "count" -> 1,
  "objects" -> objects).toJson.toString
// {"count":1,"objects":[{"bool_val":true,"string_val":"hello"},[]]}
//                          I don't know where this comes from: ^

2 个答案:

答案 0 :(得分:0)

如果你只想扔掉类型系统并投射一切:

import math._ // For BigDecimal and the associated implicit conversions

// Dispatch to another overload if possible, or crash and burn with MatchError
def toJson(any: Any): String = any match {
  case null            => "null"
  case obj: Map[_, _]  => toJson(obj.asInstanceOf[Map[String, Any]])
  case arr: Seq[_]     => toJson(arr)
  case str: String     => toJson(str)
  case  bl: Boolean    => toJson(bl)
  case  bd: BigDecimal => toJson(bd)
  // v Convert to BD so toJson(Number) doesn't need to be duplicated
  case   l: Long       => toJson(l: BigDecimal)
  case   i: Int        => toJson(i: BigDecimal)
  case   s: Short      => toJson(s: BigDecimal)
  case   c: Char       => toJson(c: BigDecimal)
  case   b: Byte       => toJson(b: BigDecimal)
  case   f: Float      => toJson(f: BigDecimal)
  case   d: Double     => toJson(d: BigDecimal)
}

def toJson(obj: Map[String, Any]): String =
  // Build a list of "key": "value" entries
  obj.foldRight(Seq.empty[String]) { case ((prop, value), building) =>
    s"${toJson(prop)}: ${toJson(value)}" +: building
  }.mkString("{ ", ", ", " }")
  // Wrap in { ... } and add ", "s

def toJson(arr: Seq[Any]): String =
  // Build list of JSON strings
  arr.foldRight(Seq.empty[String]) { (value, building) =>
    toJson(value) +: building
  }.mkString("[ ", ", ", " ]")
  // Wrap in [ ... ] and add ", "s

// Process each character, one by one
def toJson(str: String): String = str.flatMap {
  // Common escapes
  case '\\' => "\\\\"
  case '"'  => "\\\""
  case '\b' => "\\b"
  case '\f' => "\\f"
  case '\n' => "\\n"
  case '\r' => "\\r"
  case '\t' => "\\t"
  // All the other control characters
  case ctrl if ctrl < ' ' => f"\\u${ctrl}%04x"
  // Nothing special: leave unchanged
  case c  => c.toString
}.mkString("\"", "", "\"")
// Wrap in "..."

def toJson(bl: Boolean): String = bl.toString

def toJson(bd: BigDecimal): String = bd.toString

请注意,这是一个非常非Scala的事情。 Python没有像Scala这样的类型系统,这就是为什么你习惯于运行时检查这样的代码。但Scala不是那样的,你应该一起工作,而不是反对它。首先,像这样创建包含Any的地图和seq是一个巨大的红旗。考虑使用case class es来保存数据。为此,许多Scala的JSON库支持自动派生的JSON编码/解码器,通常使用shapeless.Generic

答案 1 :(得分:0)

基于https://coderwall.com/p/o--apg/easy-json-un-marshalling-in-scala-with-jackson

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper

val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)

def toJson(value: Any): String = {
  mapper.writeValueAsString(value)
}