This repository has been archived by the owner on Jan 18, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 13
JSON
Jong Wook Kim edited this page Feb 2, 2017
·
1 revision
Map, Seq 등의 자료구조나 case class를 다음과 같이 손쉽게 JSON으로 변환할 수 있습니다.
import com.kakao.mango.json._
case class Foo(hello: String)
toJson(Foo("world")) // {"hello":"world"}
toJson(1 to 10) // [1,2,3,4,5,6,7,8,9,10]
toJson(Map("hello" -> "world")) // {"hello":"world"}
parseJson
메소드를 사용하면 JSON을 Map[String, Any]
타입으로 파싱합니다. 스키마가 미리 정해지지 않은 비정형 JSON을 처리할 수 있습니다.
val json = """{"a": 3, "b": true, "c": [1,2,3], "d": {"hello": "world"}}"""
val data: Map[String, Any] = parseJson(json)
data.foreach {
case (key, value: Int) => println(s"Integer: $key => $value")
case (key, value: Boolean) => println(s"Boolean: $key => $value")
case (key, value: Seq[_]) => println(s"Seq: $key => $value")
case (key, value: Map[_,_]) => println(s"Map: $key => $value")
}
위 코드는 parseJson
이 리턴한 Map[String, Any]
의 원소들을 패턴매칭을 통해 출력하는 예로, 결과는 다음과 같습니다.
Integer: a => 3
Boolean: b => true
Seq: c => List(1, 2, 3)
Map: d => Map(hello -> world)
주어진 타입으로 파싱하고 싶은 경우엔 fromJson[타입이름]
으로 한 번에 파싱할 수 있습니다.
case class Foo(hello: String)
val json = """{"hello": "world"}"""
val foo = fromJson[Foo](json) // Foo("world")
이외에도 다음과 같은 메소드를 지원합니다. 바이트 배열은 UTF-8 인코딩을 사용합니다.
메소드 | 리턴타입 | 용도 |
---|---|---|
toJson(obj:Any) |
String |
obj 를 JSON 문자열로 변환 |
toPrettyJson(obj:Any) |
String |
obj 를 줄바꿈이 잘 된 JSON 문자열로 변환 |
serialize(obj:Any) |
Array[Byte] |
obj 를 JSON 바이트 배열로 변환 |
serializePretty(obj:Any) |
Array[Byte] |
obj 를 줄바꿈이 잘 된 JSON 바이트 배열로 변환 |
fromJson[T](src:Array[Byte]) |
T |
JSON 바이트 배열을 T 타입으로 파싱 |
fromJson[T](src:String) |
T |
JSON 문자열을 T 타입으로 파싱 |
parseJson(src:Array[Byte]) |
Map[String,Any] |
JSON을 담은 UTF-8 바이트 배열을 Map 으로 파싱 |
parseJson(src:String) |
Map[String,Any] |
JSON 문자열을 Map 으로 파싱 |
Mango JSON을 사용하다 보면 아래와 같이 예기치 않은 오류가 발생할 수 있습니다.
import com.kakao.mango.json._
case class Test(m: Map[String, Double])
val test = fromJson[Test]("""{"m":{"answer":42}}""")
println(test.m("answer")) // 42 출력
println(test.m.get("answer").get) // 42 출력
val d = test.m("answer") // 여기서 ClassCastException: java.lang.Integer cannot be cast to java.lang.Double
test.m.get("answer") match {
case Some(i) =>
println(i) // 여기서 ClassCastException: java.lang.Integer cannot be cast to java.lang.Double
case None =>
}
Jackson 라이브러리와 JVM, 스칼라 언어의 스펙과 관련하여 다음과 같은 몇 가지 상황이 겹쳐서 발생하는 상황입니다.
- JSON 스펙상에는 단 하나의 숫자 타입만 있으며, 길이나 정밀도에 대한 제한은 없습니다. * 하지만 JSON의 숫자를 파싱해서 JVM에서 다룰 때에는 여러 타입 중 하나를 사용해야 합니다.
- 스칼라의
Int
,Double
과 같은 프리미티브 타입은value: Double
처럼 그냥 사용될 때는 자바의 프리미티브 타입으로,value: Option[Double]
처럼 제너릭 파라미터로 사용될 때는 내부적으로Object
가 됩니다. - Jackson은 파싱할 숫자의 타입을 알고 있을 경우 그 타입으로, 그렇지 않거나 Object 타입이 주어져 있으면 숫자의 종류와 크기에 따라
Integer
,Long
,Float
,Double
,BigDecimal
,BigInteger
중 하나의 자바 클래스로 파싱합니다. - 그래서 위 코드에서는 메모리상의 Map에
Integer
타입의 오브젝트가 들어 있고,println
의 경우 에러가 나지 않았지만 리턴타입을 변수로 할당하면Double
로 캐스팅이 일어나면서 에러가 발생했습니다.
Jackson 스칼라 모듈의 개발자도 이 현상에 대해서 많은 고민을 하였으며, @JsonDeserialize(contentAs = classOf[java.lang.Double])
과 같은 어노테이션을 쓸 것을 추천하고 있습니다. 즉, 다음과 같이 하거나
import com.kakao.jackson.databind.annotation.JsonDeserialize
case class Test(
@JsonDeserialize(contentAs = classOf[java.lang.Double])
m: Map[String, Double]
)
아예 자바 타입을 쓸 수 있습니다.
case class Test(
m: Map[String, java.lang.Double]
)
두 경우 모두 처음과 같은 에러는 발생하지 않습니다.