forked from stevej/scala-json
/
Json.scala
144 lines (125 loc) · 4.27 KB
/
Json.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/*
* Copyright 2009 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.twitter.json
import net.lag.extensions._
import scala.collection.Map
import scala.collection.immutable.EmptyMap
import scala.util.parsing.combinator._
trait JsonSerializable {
def toJson(): String
}
/**
* An Exception thrown when parsing or building JSON.
*/
class JsonException(reason: String) extends Exception(reason)
/**
* Stolen (awesomely) from the scala book and fixed by making string quotation explicit.
*/
private class JsonParser extends JavaTokenParsers {
def obj: Parser[Map[String, Any]] = "{" ~> repsep(member, ",") <~ "}" ^^ (new EmptyMap ++ _)
def arr: Parser[List[Any]] = "[" ~> repsep(value, ",") <~ "]"
def member: Parser[(String, Any)] = string ~ ":" ~ value ^^ {
case name ~ ":" ~ value => (name, value)
}
def number: Parser[Any] = floatingPointNumber ^^ {
case num if num.matches(".*[.eE].*") => BigDecimal(num)
case num => {
val rv = num.toLong
if (rv >= Math.MIN_INT && rv <= Math.MAX_INT) rv.toInt else rv
}
}
def string: Parser[String] =
"\"" ~> """([^\"[\x00-\x1F]\\]+|\\[\\/bfnrt"]|\\u[a-fA-F0-9]{4})*""".r <~ "\"" ^^
{ _.replace("""\/""", "/").unquoteC }
def value: Parser[Any] = obj | arr | string | number |
"null" ^^ (x => null) | "true" ^^ (x => true) | "false" ^^ (x => false)
def parse(s: String) = {
parseAll(value, s) match {
case Success(result, _) => result
case x @ Failure(msg, z) => throw new JsonException(x.toString)
case x @ Error(msg, _) => throw new JsonException(x.toString)
}
}
}
/**
* An explanation of Scala types and their JSON representations.
*
* Natively supported scalar types are: Boolean, Int, Long, String.
* Collections are Seq[T], Map[String, T] where T includes the scalars defined above, or
* recursive Seq or Map. You are in flavor country.
*/
object Json {
/**
* Quote a string according to "JSON rules".
*/
def quote(s: String) = {
val charCount = s.codePointCount(0, s.length)
// TODO: Make this for loop more functional-ish
var quoted = "\""
for (i <- 0 until charCount) {
val codePoint = s.codePointAt(i)
val b = codePoint match {
case 0x0d => "\\r"
case 0x0a => "\\n"
case 0x09 => "\\t"
case 0x22 => "\\\""
case 0x5c => "\\\\"
case 0x2f => "\\/" // to avoid sending "</"
case c if c > 0xffff =>
val chars = Character.toChars(c)
("\\u%04x" format chars(0).asInstanceOf[Int]) + ("\\u%04x" format chars(1).asInstanceOf[Int])
case c if c > 0x7e => "\\u%04x" format c.asInstanceOf[Int]
case c => c.toChar
}
quoted += b
}
quoted += "\""
quoted
}
/**
* Returns a JSON representation of the given object, as a JsonQuoted object.
*/
def build(obj: Any): JsonQuoted = {
val rv = obj match {
case JsonQuoted(body) => body
case null => "null"
case x: Boolean => x.toString
case x: Number => x.toString
case list: Seq[_] =>
list.map(build(_).body).mkString("[", ",", "]")
case map: Map[_, _] =>
(for ((key, value) <- map.elements) yield {
quote(key.toString) + ":" + build(value).body
}).mkString("{", ",", "}")
case x: JsonSerializable => x.toJson()
case x =>
quote(x.toString)
}
JsonQuoted(rv)
}
/**
* Parses a JSON String representation into its native Scala reprsentation.
*/
def parse(s: String): Any = (new JsonParser).parse(s)
}
/**
* Wrapper for the JSON string representation of a data structure. This class exists to
* allow objects to be converted into JSON, attached to another data structure, and not
* re-encoded.
*/
case class JsonQuoted(body: String) {
override def toString = body
}