Skip to content

Commit f6464a0

Browse files
committed
Removing support for the undefined keyword, which is part of JavaScript but not JSON itself.
1 parent 97eb6d7 commit f6464a0

12 files changed

Lines changed: 85 additions & 44 deletions

README.markdown

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,15 +113,14 @@ For example, a PATCH request may have a payload like this:
113113
```json
114114
{ "id":"234565434567898789098765",
115115
"field1": "new value",
116-
"field3": null,
117-
"field4": undefined }
116+
"field3": null }
118117
```
119-
which would tell the server to update field1 to "new value", set field3 to null, and leave field2 and field4
120-
unchanged. With a standard scala `Option`, it is impossible to tell whether the values of field2, field3,
121-
and field4 in the original payload were `null` or `undefined` since any missing values translate to `None`.
118+
which would tell the server to update field1 to "new value", set field3 to null, and leave field2
119+
unchanged. With a standard scala `Option`, it is impossible to tell whether the values of field2 and field3
120+
in the original payload were `null` or undefined since any missing values translate to `None`.
122121

123-
The `Tription` solves that problem by defining `Value` for present values, `Null` for null values, and
124-
`Undefined` for values which are missing or explicitly marked as undefined.
122+
The `Tription` solves that problem by defining `Value` for present values, `Null` for values explicitly marked
123+
null, and `Undefined` for values which are missing.
125124

126125
`Tription`s can be used just like `Option`s:
127126
```scala
@@ -183,8 +182,8 @@ object MyJsonProtocol extends DefaultJsonProtocol {
183182
#### NullOptions
184183

185184
The `NullOptions` trait supplies an alternative rendering mode for optional case class members. Normally optional
186-
members that are undefined (`None`/`Undefined`) are not rendered at all. By mixing in this trait into your custom JsonProtocol you
187-
can enforce the rendering of undefined members as `null`.
185+
members that are undefined (`None`/`Undefined`) are not rendered at all. By mixing in this trait into your custom
186+
JsonProtocol you can enforce the rendering of undefined members as `null`.
188187
(Note that this only affect JSON writing, spray-json will always read missing `Option` members as well as `null`
189188
`Option` members as `None` and missing `Tription` members as `Undefined`.)
190189

src/main/scala/spray/json/CompactPrinter.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ trait CompactPrinter extends JsonPrinter {
3333

3434
protected def printObject(members: Map[String, JsValue], sb: StringBuilder) {
3535
sb.append('{')
36-
printSeq(members, sb.append(',')) { m =>
37-
printString(m._1, sb)
38-
sb.append(':')
39-
print(m._2, sb)
36+
val definedMembers = members filter { case (_, v) => v != JsUndefined }
37+
printSeq(definedMembers, sb.append(',')) { m =>
38+
printString( m._1, sb )
39+
sb.append( ':' )
40+
print( m._2, sb )
4041
}
4142
sb.append('}')
4243
}

src/main/scala/spray/json/JsValue.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ case class JsArray(elements: Vector[JsValue]) extends JsValue {
6868
}
6969
object JsArray {
7070
val empty = JsArray(Vector.empty)
71-
def apply(elements: JsValue*) = new JsArray(elements.toVector)
71+
def apply(elements: JsValue*) = if( elements contains JsUndefined ) throw new IllegalStateException( "JSON arrays cannot contain undefined values" )
72+
else new JsArray(elements.toVector)
7273
@deprecated("Use JsArray(Vector[JsValue]) instead", "1.3.0")
7374
def apply(elements: List[JsValue]) = new JsArray(elements.toVector)
7475
}
@@ -123,5 +124,5 @@ case object JsFalse extends JsBoolean {
123124
*/
124125
case object JsNull extends JsValue
125126

126-
/** The representation for JSON undefined. **/
127+
/** The representation for JSON missing value. **/
127128
case object JsUndefined extends JsValue

src/main/scala/spray/json/JsonParser.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ class JsonParser(input: ParserInput) {
6060
(cursorChar: @switch) match {
6161
case 'f' => simpleValue(`false`(), JsFalse)
6262
case 'n' => simpleValue(`null`(), JsNull)
63-
case 'u' => simpleValue(`undefined`(), JsUndefined)
6463
case 't' => simpleValue(`true`(), JsTrue)
6564
case '{' => advance(); `object`()
6665
case '[' => advance(); `array`()
@@ -72,7 +71,6 @@ class JsonParser(input: ParserInput) {
7271

7372
private def `false`() = advance() && ch('a') && ch('l') && ch('s') && ws('e')
7473
private def `null`() = advance() && ch('u') && ch('l') && ws('l')
75-
private def `undefined`() = advance() && ch('n') && ch('d') && ch('e') && ch('f') && ch('i') && ch('n') && ch('e') && ws('d')
7674
private def `true`() = advance() && ch('r') && ch('u') && ws('e')
7775

7876
// http://tools.ietf.org/html/rfc4627#section-2.2

src/main/scala/spray/json/JsonPrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ trait JsonPrinter extends (JsValue => String) {
4444
protected def printLeaf(x: JsValue, sb: JStringBuilder) {
4545
x match {
4646
case JsNull => sb.append("null")
47-
case JsUndefined => sb.append("undefined")
4847
case JsTrue => sb.append("true")
4948
case JsFalse => sb.append("false")
5049
case JsNumber(x) => sb.append(x)
5150
case JsString(x) => printString(x, sb)
51+
case JsUndefined => throw new IllegalStateException( "Cannot display JsUndefined" )
5252
case _ => throw new IllegalStateException
5353
}
5454
}

src/main/scala/spray/json/PrettyPrinter.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ trait PrettyPrinter extends JsonPrinter {
4040
protected def organiseMembers(members: Map[String, JsValue]): Seq[(String, JsValue)] = members.toSeq
4141

4242
protected def printObject(members: Map[String, JsValue], sb: StringBuilder, indent: Int) {
43-
sb.append("{\n")
44-
printSeq(organiseMembers(members), sb.append(",\n")) { m =>
43+
sb.append("{\n")
44+
val definedMembers = members filter { case (_, v) => v != JsUndefined }
45+
printSeq(organiseMembers(definedMembers), sb.append(",\n")) { m =>
4546
printIndent(sb, indent + Indent)
4647
printString(m._1, sb)
4748
sb.append(": ")

src/main/scala/spray/json/ProductFormats.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,17 @@ trait ProductFormats extends ProductFormatsInstances {
5151
protected def fromField[T](value: JsValue, fieldName: String)
5252
(implicit reader: JsonReader[T]) = value match {
5353
case x: JsObject if
54-
(reader.isInstanceOf[OptionFormat[_]] &
54+
(reader.isInstanceOf[OptionFormat[_]] &
5555
!x.fields.contains(fieldName)) =>
5656
None.asInstanceOf[T]
5757
case x: JsObject if
58-
(reader.isInstanceOf[TriptionFormat[_]] &
58+
(reader.isInstanceOf[TriptionFormat[_]] &
5959
!x.fields.contains(fieldName)) =>
6060
Undefined.asInstanceOf[T]
6161
case x: JsObject =>
6262
try reader.read(x.fields(fieldName))
6363
catch {
64-
case e: NoSuchElementException => Undefined
64+
case e: NoSuchElementException =>
6565
deserializationError("Object is missing required member '" + fieldName + "'", e, fieldName :: Nil)
6666
case DeserializationException(msg, cause, fieldNames) =>
6767
deserializationError(msg, cause, fieldName :: fieldNames)

src/main/scala/spray/json/Tription.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ package spray.json
1111
* "field3: null }
1212
* </code>
1313
* which would tell the server to update field1 to 7, set field3 to null, and leave field2 alone.
14-
* With a standard scala `Option`, it is impossible to tell whether the payload of the request had field2 and field3
14+
* With a standard scala `Option`, it is impossible to tell whether field2 and field3 were
1515
* null or undefined since any missing values translate to `None`.
1616
*
17-
* The Tription solves that problem by defining `Value` for present values, `Null` for null values, and
18-
* `Undefined` for values which are missing or explicitly marked as undefined.
17+
* The Tription solves that problem by defining `Value` for present values, `Null` for values
18+
* explicitly set to null, and `Undefined` for values which are not there at all.
1919
*
2020
* Created by bathalh on 2/19/16.
2121
*/

src/test/scala/spray/json/CompactPrinterSpec.scala

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,13 @@ class CompactPrinterSpec extends Specification {
2424
"print JsNull to 'null'" in {
2525
CompactPrinter(JsNull) mustEqual "null"
2626
}
27-
"print JsUndefined to 'undefined'" in {
28-
CompactPrinter(JsUndefined) mustEqual "undefined"
27+
"throw exception when printing JsUndefined" in {
28+
try {
29+
CompactPrinter(JsUndefined) mustEqual "undefined"
30+
} catch {
31+
case ise: IllegalStateException =>
32+
ise.getMessage mustEqual "Cannot display JsUndefined"
33+
}
2934
}
3035
"print JsTrue to 'true'" in {
3136
CompactPrinter(JsTrue) mustEqual "true"
@@ -67,9 +72,17 @@ class CompactPrinterSpec extends Specification {
6772
CompactPrinter(JsObject("key" -> JsNumber(42), "key2" -> JsString("value")))
6873
mustEqual """{"key":42,"key2":"value"}"""
6974
)
75+
"properly print a simple JsObject with undefined values" in (
76+
CompactPrinter(JsObject("key" -> JsNumber(42), "key2" -> JsString("value"), "key3" -> JsUndefined))
77+
mustEqual """{"key":42,"key2":"value"}"""
78+
)
79+
"properly print a simple JsObject with only undefined values" in (
80+
CompactPrinter(JsObject("key" -> JsUndefined, "key2" -> JsUndefined))
81+
mustEqual "{}"
82+
)
7083
"properly print a simple JsArray" in (
71-
CompactPrinter(JsArray(JsNull, JsUndefined, JsNumber(1.23), JsObject("key" -> JsBoolean(true))))
72-
mustEqual """[null,undefined,1.23,{"key":true}]"""
84+
CompactPrinter(JsArray(JsNull, JsNumber(1.23), JsObject("key" -> JsBoolean(true))))
85+
mustEqual """[null,1.23,{"key":true}]"""
7386
)
7487
"properly print a JSON padding (JSONP) if requested" in {
7588
CompactPrinter(JsTrue, Some("customCallback")) mustEqual("customCallback(true)")

src/test/scala/spray/json/JsonParserSpec.scala

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@ class JsonParserSpec extends Specification {
2424
"parse 'null' to JsNull" in {
2525
JsonParser("null") === JsNull
2626
}
27-
"parse 'undefined' to JsUndefined" in {
28-
JsonParser("undefined") === JsUndefined
29-
}
3027
"parse 'true' to JsTrue" in {
3128
JsonParser("true") === JsTrue
3229
}
@@ -60,8 +57,8 @@ class JsonParserSpec extends Specification {
6057
JsObject("key" -> JsNumber(42), "key2" -> JsString("value"))
6158
)
6259
"parse a simple JsArray" in (
63-
JsonParser("""[null, undefined, 1.23 ,{"key":true } ] """) ===
64-
JsArray(JsNull, JsUndefined, JsNumber(1.23), JsObject("key" -> JsTrue))
60+
JsonParser("""[null, 1.23 ,{"key":true } ] """) ===
61+
JsArray(JsNull, JsNumber(1.23), JsObject("key" -> JsTrue))
6562
)
6663
"parse directly from UTF-8 encoded bytes" in {
6764
val json = JsObject(

0 commit comments

Comments
 (0)