summaryrefslogtreecommitdiff
path: root/src/main/scala/cc/spray/json/JsValue.scala
blob: a02de0fc7df7191e08f80032f65ca699fa07ddc2 (plain) (blame)
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
/*
 * Copyright (C) 2009-2011 Mathias Doenitz
 * Inspired by a similar implementation by Nathan Hamblen
 * (https://github.com/n8han/Databinder-Dispatch)
 *
 * 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 cc.spray.json

import collection.mutable.ListBuffer

/**
  * The general type of a JSON AST node.
 */
sealed trait JsValue {
  override def toString = CompactPrinter(this)
  def toString(printer: (JsValue => String)) = printer(this)
  def fromJson[T :JsonReader]: T = jsonReader[T].read(this)
}
object JsValue {

  /**
    * General converter to a JsValue.
    * Throws an IllegalArgumentException if the given value cannot be converted.
   */
  def apply(value: Any): JsValue = value match {
    case null => JsNull
    case true => JsTrue
    case false => JsFalse
    case x: JsValue => x
    case x: String => JsString(x)
    case x: Int => JsNumber(x)
    case x: Long => JsNumber(x)
    case x: Double => JsNumber(x)
    case x: Char => JsString(String.valueOf(x))
    case x: Float => JsNumber(x)
    case x: Byte => JsNumber(x)
    case x: Short => JsNumber(x)
    case x: BigInt => JsNumber(x)
    case x: BigDecimal => JsNumber(x)
    case x: Symbol => JsString(x.name)
    case x: collection.Map[_, _] => JsObject(fromSeq(x))
    case x@ collection.Seq((_, _), _*) => JsObject(fromSeq(x.asInstanceOf[Seq[(_, _)]]))
    case x: collection.Seq[_] => JsArray(x.toList.map(JsValue.apply))
    case x => throw new IllegalArgumentException(x.toString + " cannot be converted to a JsValue")
  }
  
  private def fromSeq(seq: Iterable[(_, _)]) = {
    val list = ListBuffer.empty[JsField]
    seq.foreach {
      case (key: String, value) => list += JsField(key, JsValue(value))
      case (key: Symbol, value) => list += JsField(key.name, JsValue(value))
      case (key: JsString, value) => list += JsField(key.value, JsValue(value))
      case (x, _) => throw new IllegalArgumentException(x.toString + " cannot be converted to a JsString")
    }
    list.toList
  }
}

/**
  * A JSON object.
 */
case class JsObject(fields: List[JsField]) extends JsValue {
  lazy val asMap: Map[String, JsValue] = {
    val b = Map.newBuilder[String, JsValue]
    for (JsField(name, value) <- fields) b += ((name, value))
    b.result()
  }
}
object JsObject {
  def apply(members: JsField*) = new JsObject(members.toList)
}

/**
  * The members/fields of a JSON object.
 */
case class JsField(name: String, value: JsValue) extends JsValue
object JsField {
  def apply(name: String, value: Any) = new JsField(name, JsValue(value))
}

/**
  * A JSON array.
 */
case class JsArray(elements: List[JsValue]) extends JsValue
object JsArray {
  def apply(elements: JsValue*) = new JsArray(elements.toList)
}

/**
  * A JSON string.
 */
case class JsString(value: String) extends JsValue

/**
  * A JSON number.
 */
case class JsNumber(value: BigDecimal) extends JsValue
object JsNumber {
  def apply(n: Int) = new JsNumber(BigDecimal(n))
  def apply(n: Long) = new JsNumber(BigDecimal(n))
  def apply(n: Double) = new JsNumber(BigDecimal(n))
  def apply(n: BigInt) = new JsNumber(BigDecimal(n))
  def apply(n: String) = new JsNumber(BigDecimal(n))
}

/**
  * JSON Booleans.
 */
sealed trait JsBoolean extends JsValue {
  def value: Boolean
}
object JsBoolean {
  def apply(x: Boolean): JsBoolean = if (x) JsTrue else JsFalse
  def unapply(x: JsBoolean): Option[Boolean] = Some(x.value)
}
case object JsTrue extends JsBoolean {
  def value = true
}
case object JsFalse extends JsBoolean {
  def value = false
}

/**
  * The representation for JSON null.
 */
case object JsNull extends JsValue