From 5b2bfa93c7a13d7c55e49ecbaa87bd76a30147d7 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Tue, 20 Aug 2013 11:46:46 +0200 Subject: add new lens `arrayOrSingletonAsArray` which reinterprets non json array values as singleton arrays, fixes #8 --- README.md | 2 ++ notes/0.5.4.markdown | 7 ++++++ .../scala/spray/json/lenses/ScalarLenses.scala | 16 +++++++++++- .../scala/spray/json/lenses/JsonLensesSpec.scala | 29 ++++++++++++++++++++++ .../spray/json/lenses/JsonPathExamplesSpec.scala | 21 ++++++++++++++++ 5 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 notes/0.5.4.markdown diff --git a/README.md b/README.md index 26f0cf5..b053a7f 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,8 @@ from the following list. and its field `fullName` is a string starting with `"Joe"`. * `allMatching(next: Lens)`: A combination of `combine` and `elements`. Selects elements matching the `next` lens and then applies the `next` lens. + * `arrayOrSingletonAsArray`: Makes sure that the result is always a json array by interpreting a value that is no array + as an singleton array containing just that value. ##### Combination diff --git a/notes/0.5.4.markdown b/notes/0.5.4.markdown new file mode 100644 index 0000000..861208a --- /dev/null +++ b/notes/0.5.4.markdown @@ -0,0 +1,7 @@ +New features in this version: + + - [#8][] added `arrayOrSingletonAsArray` which allows to reinterpret values as singleton arrays + - [#11][] fixed a parser bug in JsonPathParser + +[#8]: https://github.com/jrudolph/json-lenses/issues/8 +[#11]: https://github.com/jrudolph/json-lenses/issues/11 diff --git a/src/main/scala/spray/json/lenses/ScalarLenses.scala b/src/main/scala/spray/json/lenses/ScalarLenses.scala index 3a85147..4beee27 100644 --- a/src/main/scala/spray/json/lenses/ScalarLenses.scala +++ b/src/main/scala/spray/json/lenses/ScalarLenses.scala @@ -55,6 +55,20 @@ trait ScalarLenses { def retr: JsValue => SafeJsValue = x => Right(x) } + + /** + * A lens which leaves JsArray as is but transforms any other kind of JsValue into + * a singleton JsArray with that value as single element. + */ + val arrayOrSingletonAsArray: ScalarLens = new LensImpl[Id] { + def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue = + retr(parent).flatMap(x => f(Right(x))) + + def retr: JsValue => Validated[JsValue] = { + case ar: JsArray => Right(ar) + case x => Right(JsArray(x)) + } + } } -object ScalarLenses extends ScalarLenses \ No newline at end of file +object ScalarLenses extends ScalarLenses diff --git a/src/test/scala/spray/json/lenses/JsonLensesSpec.scala b/src/test/scala/spray/json/lenses/JsonLensesSpec.scala index 780083c..9418fb1 100644 --- a/src/test/scala/spray/json/lenses/JsonLensesSpec.scala +++ b/src/test/scala/spray/json/lenses/JsonLensesSpec.scala @@ -66,6 +66,20 @@ class JsonLensesSpec extends Specification with SpecHelpers { } } } + "orSingletonArray" in { + "empty JsArray" in { + "[]".extract[Seq[Int]](arrayOrSingletonAsArray) === Nil + } + "filled JsArray" in { + "[1,2,3,4,5]".extract[Seq[Int]](arrayOrSingletonAsArray) === Seq(1, 2, 3, 4, 5) + } + "single int value" in { + "5".extract[Seq[Int]](arrayOrSingletonAsArray) === Seq(5) + } + "single object value" in { + """{ "a": 5 }""".extract[Seq[Map[String, Int]]](arrayOrSingletonAsArray) === Seq(Map("a" -> 5)) + } + } "all elements of an array" in { "simple" in { """[18, 23, 2, 5, 8, 3]""".extract[Int](*) must be_==(Seq(18, 23, 2, 5, 8, 3)) @@ -88,6 +102,7 @@ class JsonLensesSpec extends Specification with SpecHelpers { """{"a": 5}""".extract[Int](("a" / *)) must throwAn[Exception]( """Not a json array: 5""") } } + /*"filtered elements of an array" in { }*/ @@ -161,6 +176,20 @@ class JsonLensesSpec extends Specification with SpecHelpers { "set element of array" in { """["a", "b", 2, 5, 8, 3]""" update (element(3) ! set(35)) must be_json( """["a", "b", 2, 35, 8, 3]""") } + "orSingletonArray" in { + "empty JsArray" in { + "[]".update(arrayOrSingletonAsArray ! set(Seq(1, 2, 3, 4, 5))) must be_json("""[1, 2, 3, 4, 5]""") + } + "filled JsArray" in { + "[1,2,3,4,5]".update(arrayOrSingletonAsArray / * ! modify[Int](_ + 1)) must be_json("""[2, 3, 4, 5, 6]""") + } + "single int value" in { + "5".update(arrayOrSingletonAsArray / * ! modify[Int](_ + 1)) must be_json("""[6]""") + } + "single object value" in { + """{ "a": 5 }""".update(arrayOrSingletonAsArray / * / 'a ! modify[Int](_ + 1)) must be_json("""[{ "a" : 6 }]""") + } + } "change a found element" in { "in a homogenuous array" in { "if found" in { diff --git a/src/test/scala/spray/json/lenses/JsonPathExamplesSpec.scala b/src/test/scala/spray/json/lenses/JsonPathExamplesSpec.scala index 707671b..0b43039 100644 --- a/src/test/scala/spray/json/lenses/JsonPathExamplesSpec.scala +++ b/src/test/scala/spray/json/lenses/JsonPathExamplesSpec.scala @@ -38,6 +38,24 @@ class JsonPathExamplesSpec extends Specification with SpecHelpers { |} """.stripMargin) + val singleElemArray = JsonParser( + """ + |{ "store": { + | "book": + | { "category": "reference", + | "author": "Nigel Rees", + | "title": "Sayings of the Century", + | "price": 8.95 + | } + | , + | "bicycle": { + | "color": "red", + | "price": 19.95 + | } + | } + |} + """.stripMargin) + "Examples" should { "with Scala syntax" in { "All authors" in { @@ -46,6 +64,9 @@ class JsonPathExamplesSpec extends Specification with SpecHelpers { "Author of first book" in { json.extract[String](("store" / "book" / element(0) / "author")) must be_==("Nigel Rees") } + "Author of first book no array" in { + singleElemArray.extract[String](("store" / "book" / arrayOrSingletonAsArray / element(0) / "author")) must be_==("Nigel Rees") + } "Books with category 'reference'" in { json.extract[String](("store" / "book" / filter("category".is[String](_ == "reference")) / "title")) must be_==(Seq("Sayings of the Century")) } -- cgit v1.2.3