summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/library/scala/util/parsing/json/JSON.scala52
-rw-r--r--src/library/scala/util/parsing/json/Parser.scala29
-rw-r--r--test/files/run/json.check21
-rw-r--r--test/files/run/json.scala97
4 files changed, 163 insertions, 36 deletions
diff --git a/src/library/scala/util/parsing/json/JSON.scala b/src/library/scala/util/parsing/json/JSON.scala
index 1be8a10931..6d3761af52 100644
--- a/src/library/scala/util/parsing/json/JSON.scala
+++ b/src/library/scala/util/parsing/json/JSON.scala
@@ -41,8 +41,33 @@ object JSON extends Parser {
*
* @param input the given JSON string.
* @return an optional list of of elements.
+ *
+ * @deprecated Use parseFull or parseRaw as needed.
+ */
+ def parse(input: String): Option[List[Any]] = parseRaw(input).map(unRaw).flatMap({
+ case l : List[_] => Some(l)
+ case _ => None
+ })
+
+ /**
+ * This method converts "raw" results back into the original, deprecated
+ * form.
*/
- def parse(input: String): Option[List[Any]] =
+ private def unRaw (in : Any) : Any = in match {
+ case JSONObject(obj) => obj.map({ case (k,v) => (k,unRaw(v))}).toList
+ case JSONArray(list) => list.map(unRaw)
+ case x => x
+ }
+
+ /**
+ * Parse the given JSON string and return a list of elements. If the
+ * string is a JSON object it will be a JSONObject. If it's a JSON
+ * array it will be be a JSONArray.
+ *
+ * @param input the given JSON string.
+ * @return an optional JSONType element.
+ */
+ def parseRaw(input : String) : Option[JSONType] =
phrase(root)(new lexical.Scanner(input)) match {
case Success(result, _) => Some(result)
case _ => None
@@ -57,7 +82,7 @@ object JSON extends Parser {
* @return an optional list or map.
*/
def parseFull(input: String): Option[Any] =
- parse(input) match {
+ parseRaw(input) match {
case Some(data) => Some(resolveType(data))
case None => None
}
@@ -66,23 +91,12 @@ object JSON extends Parser {
* A utility method to resolve a parsed JSON list into objects or
* arrays. See the parse method for details.
*/
- def resolveType(input: List[_]): Any = {
- var objMap = Map[String, Any]()
-
- if (input.forall {
- case (key: String, value: List[_]) =>
- objMap = objMap.+[Any](key -> resolveType(value))
- true
- case (key : String, value) =>
- objMap += key -> value
- true
- case _ => false
- }) objMap
- else
- input.map {
- case l : List[_] => resolveType(l)
- case x => x
- }
+ def resolveType(input: Any): Any = input match {
+ case JSONObject(data) => data.transform {
+ case (k,v) => resolveType(v)
+ }
+ case JSONArray(data) => data.map(resolveType)
+ case x => x
}
/**
diff --git a/src/library/scala/util/parsing/json/Parser.scala b/src/library/scala/util/parsing/json/Parser.scala
index 0d573edce4..e8e7897ca4 100644
--- a/src/library/scala/util/parsing/json/Parser.scala
+++ b/src/library/scala/util/parsing/json/Parser.scala
@@ -15,6 +15,31 @@ import scala.util.parsing.combinator.syntactical._
import scala.util.parsing.combinator.lexical._
/**
+ * A marker class for the JSON result types.
+ *
+ * @author Derek Chen-Becker <"java"+@+"chen-becker"+"."+"org">
+ */
+sealed abstract class JSONType
+
+/**
+ * Represents a JSON Object (map).
+ * @author Derek Chen-Becker <"java"+@+"chen-becker"+"."+"org">
+ */
+case class JSONObject (obj : Map[Any,Any]) extends JSONType {
+ override def toString = "JSONObject(" + obj.map({ case (k,v) => k + " -> " + v }).mkString(", ") + ")"
+}
+
+/**
+ * Represents a JSON Array (list).
+ * @author Derek Chen-Becker <"java"+@+"chen-becker"+"."+"org">
+ */
+case class JSONArray (list : List[Any]) extends JSONType {
+ override def toString = "JSONArray(" + list.mkString(", ") + ")"
+}
+
+/**
+ * The main JSON Parser.
+ *
* @author Derek Chen-Becker <"java"+@+"chen-becker"+"."+"org">
*/
class Parser extends StdTokenParsers with ImplicitConversions {
@@ -39,8 +64,8 @@ class Parser extends StdTokenParsers with ImplicitConversions {
// Define the grammar
def root = jsonObj | jsonArray
- def jsonObj = "{" ~> repsep(objEntry, ",") <~ "}"
- def jsonArray = "[" ~> repsep(value, ",") <~ "]"
+ def jsonObj = "{" ~> repsep(objEntry, ",") <~ "}" ^^ { case vals : List[_] => JSONObject(Map(vals : _*)) }
+ def jsonArray = "[" ~> repsep(value, ",") <~ "]" ^^ { case vals : List[_] => JSONArray(vals) }
def objEntry = stringVal ~ (":" ~> value) ^^ { case x ~ y => (x, y) }
def value: Parser[Any] = (jsonObj | jsonArray | number | "true" ^^^ true | "false" ^^^ false | "null" ^^^ null | stringVal)
def stringVal = accept("string", { case lexical.StringLit(n) => n} )
diff --git a/test/files/run/json.check b/test/files/run/json.check
index a735624221..021214beaa 100644
--- a/test/files/run/json.check
+++ b/test/files/run/json.check
@@ -1,12 +1,17 @@
-Some(List((name,value)))
-Some(List((name,va1ue)))
-Some(List((name,List((name1,va1ue1), (name2,va1ue2)))))
-Some(List((name,")))
-Some(List((age,0.0)))
+Passed: Map(name -> value)
+Passed: Map(name -> va1ue)
+Passed: Map(name -> Map(name1 -> va1ue1, name2 -> va1ue2))
+Passed: Map(name -> ")
+Passed: Map(function -> add_symbol)
+Passed: List(Map(a -> team), Map(b -> 52.0))
+Passed: Map()
+Passed: List()
+Passed: List(4.0, 1.0, 3.0, 2.0, 6.0, 5.0, 8.0, 7.0)
+Passed: Map(age -> 0.0)
-Some(List((firstName,John), (lastName,Smith), (address,List((streetAddress,21 2nd Street), (city,New York), (state,NY), (postalCode,10021.0))), (phoneNumbers,List(212 732-1234, 646 123-4567))))
+Passed: Map(firstName -> John, lastName -> Smith, address -> Map(streetAddress -> 21 2nd Street, city -> New York, state -> NY, postalCode -> 10021.0), phoneNumbers -> List(212 732-1234, 646 123-4567))
-Some(List((fullname,Sean Kelly), (org,SK Consulting), (emailaddrs,List(List((type,work), (value,kelly@seankelly.biz)), List((type,home), (pref,1.0), (value,kelly@seankelly.tv)))), (telephones,List(List((type,work), (pref,1.0), (value,+1 214 555 1212)), List((type,fax), (value,+1 214 555 1213)), List((type,mobile), (value,+1 214 555 1214)))), (addresses,List(List((type,work), (format,us), (value,1234 Main StnSpringfield, TX 78080-1216)), List((type,home), (format,us), (value,5678 Main StnSpringfield, TX 78080-1316)))), (urls,List(List((type,work), (value,http://seankelly.biz/)), List((type,home), (value,http://seankelly.tv/))))))
+Passed: Map(addresses -> List(Map(format -> us, type -> work, value -> 1234 Main StnSpringfield, TX 78080-1216), Map(format -> us, type -> home, value -> 5678 Main StnSpringfield, TX 78080-1316)), emailaddrs -> List(Map(type -> work, value -> kelly@seankelly.biz), Map(pref -> 1.0, type -> home, value -> kelly@seankelly.tv)), fullname -> Sean Kelly, org -> SK Consulting, telephones -> List(Map(pref -> 1.0, type -> work, value -> +1 214 555 1212), Map(type -> fax, value -> +1 214 555 1213), Map(type -> mobile, value -> +1 214 555 1214)), urls -> List(Map(type -> work, value -> http://seankelly.biz/), Map(type -> home, value -> http://seankelly.tv/)))
-Some(List((web-app,List((servlet,List(List((servlet-name,cofaxCDS), (servlet-class,org.cofax.cds.CDSServlet), (init-param,List((configGlossary:installationAt,Philadelphia, PA), (configGlossary:adminEmail,ksm@pobox.com), (configGlossary:poweredBy,Cofax), (configGlossary:poweredByIcon,/images/cofax.gif), (configGlossary:staticPath,/content/static), (templateProcessorClass,org.cofax.WysiwygTemplate), (templateLoaderClass,org.cofax.FilesTemplateLoader), (templatePath,templates), (templateOverridePath,), (defaultListTemplate,listTemplate.htm), (defaultFileTemplate,articleTemplate.htm), (useJSP,false), (jspListTemplate,listTemplate.jsp), (jspFileTemplate,articleTemplate.jsp), (cachePackageTagsTrack,200.0), (cachePackageTagsStore,200.0), (cachePackageTagsRefresh,60.0), (cacheTemplatesTrack,100.0), (cacheTemplatesStore,50.0), (cacheTemplatesRefresh,15.0), (cachePagesTrack,200.0), (cachePagesStore,100.0), (cachePagesRefresh,10.0), (cachePagesDirtyRead,10.0), (searchEngineListTemplate,forSearchEnginesList.htm), (searchEngineFileTemplate,forSearchEngines.htm), (searchEngineRobotsDb,WEB-INF/robots.db), (useDataStore,true), (dataStoreClass,org.cofax.SqlDataStore), (redirectionClass,org.cofax.SqlRedirection), (dataStoreName,cofax), (dataStoreDriver,com.microsoft.jdbc.sqlserver.SQLServerDriver), (dataStoreUrl,jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon), (dataStoreUser,sa), (dataStorePassword,dataStoreTestQuery), (dataStoreTestQuery,SET NOCOUNT ON;select test='test';), (dataStoreLogFile,/usr/local/tomcat/logs/datastore.log), (dataStoreInitConns,10.0), (dataStoreMaxConns,100.0), (dataStoreConnUsageLimit,100.0), (dataStoreLogLevel,debug), (maxUrlLength,500.0)))), List((servlet-name,cofaxEmail), (servlet-class,org.cofax.cds.EmailServlet), (init-param,List((mailHost,mail1), (mailHostOverride,mail2)))), List((servlet-name,cofaxAdmin), (servlet-class,org.cofax.cds.AdminServlet)), List((servlet-name,fileServlet), (servlet-class,org.cofax.cds.FileServlet)), List((servlet-name,cofaxTools), (servlet-class,org.cofax.cms.CofaxToolsServlet), (init-param,List((templatePath,toolstemplates/), (log,1.0), (logLocation,/usr/local/tomcat/logs/CofaxTools.log), (logMaxSize,), (dataLog,1.0), (dataLogLocation,/usr/local/tomcat/logs/dataLog.log), (dataLogMaxSize,), (removePageCache,/content/admin/remove?cache=pages&id=), (removeTemplateCache,/content/admin/remove?cache=templates&id=), (fileTransferFolder,/usr/local/tomcat/webapps/content/fileTransferFolder), (lookInContext,1.0), (adminGroupID,4.0), (betaServer,true)))))), (servlet-mapping,List((cofaxCDS,/), (cofaxEmail,/cofaxutil/aemail/*), (cofaxAdmin,/admin/*), (fileServlet,/static/*), (cofaxTools,/tools/*))), (taglib,List((taglib-uri,cofax.tld), (taglib-location,/WEB-INF/tlds/cofax.tld)))))))
+Passed: Map(web-app -> Map(servlet -> List(Map(init-param -> Map(cachePackageTagsRefresh -> 60.0, cachePackageTagsStore -> 200.0, cachePackageTagsTrack -> 200.0, cachePagesDirtyRead -> 10.0, cachePagesRefresh -> 10.0, cachePagesStore -> 100.0, cachePagesTrack -> 200.0, cacheTemplatesRefresh -> 15.0, cacheTemplatesStore -> 50.0, cacheTemplatesTrack -> 100.0, configGlossary:adminEmail -> ksm@pobox.com, configGlossary:installationAt -> Philadelphia, PA, configGlossary:poweredBy -> Cofax, configGlossary:poweredByIcon -> /images/cofax.gif, configGlossary:staticPath -> /content/static, dataStoreClass -> org.cofax.SqlDataStore, dataStoreConnUsageLimit -> 100.0, dataStoreDriver -> com.microsoft.jdbc.sqlserver.SQLServerDriver, dataStoreInitConns -> 10.0, dataStoreLogFile -> /usr/local/tomcat/logs/datastore.log, dataStoreLogLevel -> debug, dataStoreMaxConns -> 100.0, dataStoreName -> cofax, dataStorePassword -> dataStoreTestQuery, dataStoreTestQuery -> SET NOCOUNT ON;select test='test';, dataStoreUrl -> jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon, dataStoreUser -> sa, defaultFileTemplate -> articleTemplate.htm, defaultListTemplate -> listTemplate.htm, jspFileTemplate -> articleTemplate.jsp, jspListTemplate -> listTemplate.jsp, maxUrlLength -> 500.0, redirectionClass -> org.cofax.SqlRedirection, searchEngineFileTemplate -> forSearchEngines.htm, searchEngineListTemplate -> forSearchEnginesList.htm, searchEngineRobotsDb -> WEB-INF/robots.db, templateLoaderClass -> org.cofax.FilesTemplateLoader, templateOverridePath -> , templatePath -> templates, templateProcessorClass -> org.cofax.WysiwygTemplate, useDataStore -> true, useJSP -> false), servlet-class -> org.cofax.cds.CDSServlet, servlet-name -> cofaxCDS), Map(init-param -> Map(mailHost -> mail1, mailHostOverride -> mail2), servlet-class -> org.cofax.cds.EmailServlet, servlet-name -> cofaxEmail), Map(servlet-class -> org.cofax.cds.AdminServlet, servlet-name -> cofaxAdmin), Map(servlet-class -> org.cofax.cds.FileServlet, servlet-name -> fileServlet), Map(init-param -> Map(adminGroupID -> 4.0, betaServer -> true, dataLog -> 1.0, dataLogLocation -> /usr/local/tomcat/logs/dataLog.log, dataLogMaxSize -> , fileTransferFolder -> /usr/local/tomcat/webapps/content/fileTransferFolder, log -> 1.0, logLocation -> /usr/local/tomcat/logs/CofaxTools.log, logMaxSize -> , lookInContext -> 1.0, removePageCache -> /content/admin/remove?cache=pages&id=, removeTemplateCache -> /content/admin/remove?cache=templates&id=, templatePath -> toolstemplates/), servlet-class -> org.cofax.cms.CofaxToolsServlet, servlet-name -> cofaxTools)), servlet-mapping -> Map(cofaxAdmin -> /admin/*, cofaxCDS -> /, cofaxEmail -> /cofaxutil/aemail/*, cofaxTools -> /tools/*, fileServlet -> /static/*), taglib -> Map(taglib-location -> /WEB-INF/tlds/cofax.tld, taglib-uri -> cofax.tld)))
diff --git a/test/files/run/json.scala b/test/files/run/json.scala
index ec0bad7ebe..0a141dc38c 100644
--- a/test/files/run/json.scala
+++ b/test/files/run/json.scala
@@ -1,14 +1,80 @@
import scala.util.parsing.json._
+import scala.collection.immutable.TreeMap
object Test extends Application {
- def printJSON(s: String) {
- println(JSON parse s)
+ /* This method converts parsed JSON back into real JSON notation with objects in
+ * sorted-key order. Not required by the spec, but it allows us to to a stable
+ * toString comparison. */
+ def jsonToString(in : Any) : String = in match {
+ case l : List[_] => "[" + l.map(jsonToString).mkString(", ") + "]"
+ case m : Map[String,_] => "{" + m.elements.toList
+ .sort({ (x,y) => x._1 < y._1 })
+ .map({ case (k,v) => "\"" + k + "\": " + jsonToString(v) })
+ .mkString(", ") + "}"
+ case s : String => "\"" + s + "\""
+ case x => x.toString
}
- printJSON("{\"name\": \"value\"}")
- printJSON("{\"name\": \"va1ue\"}") // ticket #136
- printJSON("{\"name\": { \"name1\": \"va1ue1\", \"name2\": \"va1ue2\" } }")
+
+ def sortJSON(in : Any) : Any = in match {
+ case l : List[_] => l.map(sortJSON)
+ case m : Map[String,_] => TreeMap(m.mapElements(sortJSON).elements.toSeq : _*)
+ case x => x
+ }
+
+ // For this one, just parsing should be considered a pass
+ def printJSON(given : String) {
+ JSON parseFull given match {
+ case None => println("Parse failed for \"%s\"".format(given))
+ case Some(parsed) => println("Passed: " + sortJSON(parsed))
+ }
+ }
+
+ def printJSON(given : String, expected : Any) {
+ JSON parseFull given match {
+ case None => println("Parse failed for \"%s\"".format(given))
+ case Some(parsed) => if (parsed == expected) {
+ println("Passed: " + parsed)
+ } else {
+ val eStr = sortJSON(expected).toString
+ val pStr = sortJSON(parsed).toString
+
+ // Figure out where the Strings differ and generate a marker
+ val mismatchPosition = eStr.toList.zip(pStr.toList).findIndexOf({case (a,b) => a != b}) match {
+ case -1 => Math.min(eStr.length, pStr.length)
+ case x => x
+ }
+ val reason = (" " * mismatchPosition) + "^"
+ println("Expected, got:\n %s\n %s (from \"%s\")\n %s".format(eStr, pStr, given, reason))
+ }
+ }
+ }
+
+ // The library should differentiate between lower case "l" and number "1" (ticket #136)
+ printJSON("{\"name\": \"value\"}", Map("name" -> "value"))
+ printJSON("{\"name\": \"va1ue\"}", Map("name" -> "va1ue"))
+ printJSON("{\"name\": { \"name1\": \"va1ue1\", \"name2\": \"va1ue2\" } }",
+ Map("name" -> Map("name1" -> "va1ue1", "name2" -> "va1ue2")))
+
+ // Unicode escapes should be handled properly
printJSON("{\"name\": \"\\u0022\"}")
+
+ // The library should return a map for JSON objects (ticket #873)
+ printJSON("""{"function":"add_symbol"}""", Map("function" -> "add_symbol"))
+
+ // The library should recurse into arrays to find objects (ticket #2207)
+ printJSON("""[{"a": "team"},{"b": 52}]""", List(Map("a" -> "team"), Map("b" -> 52.0)))
+
+ // The library should differentiate between empty maps and lists (ticket #3284)
+ printJSON("{}", Map())
+ printJSON("[]", List())
+
+ // Lists should be returned in the same order as specified
+ printJSON("[4,1,3,2,6,5,8,7]", List[Double](4,1,3,2,6,5,8,7))
+
+ // Additional tests
printJSON("{\"age\": 0}")
+
+
println
// from http://en.wikipedia.org/wiki/JSON
@@ -27,8 +93,25 @@ object Test extends Application {
"646 123-4567"
]
}"""
- //println(sample1)
- printJSON(sample1)
+
+ // Should be equivalent to:
+ val sample1Obj = Map(
+ "firstName" -> "John",
+ "lastName" -> "Smith",
+ "address" -> Map(
+ "streetAddress" -> "21 2nd Street",
+ "city" -> "New York",
+ "state" -> "NY",
+ "postalCode" -> 10021
+ ),
+ "phoneNumbers"-> List(
+ "212 732-1234",
+ "646 123-4567"
+ )
+ )
+
+
+ printJSON(sample1, sample1Obj)
println
// from http://www.developer.com/lang/jscript/article.php/3596836