From 8ad0b7d225ddeba628b94361abfdc71ce0472b0e Mon Sep 17 00:00:00 2001 From: Aleksandr Chermenin Date: Tue, 25 Jun 2019 20:02:06 +0500 Subject: [PATCH 1/4] Jackson was replaced by Play JSON library --- dependencies.sbt | 8 +- src/main/scala/io/gatling/jsonpath/AST.scala | 29 +- .../jsonpath/ComparisonOperators.scala | 94 +- .../scala/io/gatling/jsonpath/JsonPath.scala | 121 +- .../scala/io/gatling/jsonpath/Parser.scala | 11 +- .../jsonpath/RecursiveDataIterator.scala | 25 +- .../jsonpath/RecursiveFieldIterator.scala | 47 +- .../gatling/jsonpath/RecursiveIterator.scala | 10 +- .../jsonpath/RecursiveNodeIterator.scala | 32 +- .../jsonpath/ComparisonOperatorsSpec.scala | 86 +- .../io/gatling/jsonpath/JsonPathSpec.scala | 1595 ++++++++--------- 11 files changed, 997 insertions(+), 1061 deletions(-) diff --git a/dependencies.sbt b/dependencies.sbt index ccca1e4..ea36da6 100644 --- a/dependencies.sbt +++ b/dependencies.sbt @@ -1,4 +1,4 @@ -libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2" -libraryDependencies += "com.fasterxml.jackson.core" % "jackson-databind" % "2.9.9" -libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test" -libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.14.0" % "test" +libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2" +libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test" +libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.14.0" % "test" +libraryDependencies += "com.typesafe.play" %% "play-json" % "2.7.4" \ No newline at end of file diff --git a/src/main/scala/io/gatling/jsonpath/AST.scala b/src/main/scala/io/gatling/jsonpath/AST.scala index e007137..b2a97e4 100644 --- a/src/main/scala/io/gatling/jsonpath/AST.scala +++ b/src/main/scala/io/gatling/jsonpath/AST.scala @@ -16,8 +16,7 @@ package io.gatling.jsonpath -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.node.{ BooleanNode, DoubleNode, LongNode, NullNode, TextNode } +import play.api.libs.json.{ JsBoolean, JsNull, JsNumber, JsString, JsValue } object AST { sealed trait AstToken @@ -37,30 +36,37 @@ object AST { * Slicing of an array, indices start at zero * * @param start is the first item that you want (of course) - * @param stop is the first item that you do not want - * @param step, being positive or negative, defines whether you are moving + * @param stop is the first item that you do not want + * @param step being positive or negative, defines whether you are moving */ case class ArraySlice(start: Option[Int], stop: Option[Int], step: Int = 1) extends ArrayAccessor + object ArraySlice { val All = ArraySlice(None, None) } + case class ArrayRandomAccess(indices: List[Int]) extends ArrayAccessor // JsonPath Filter AST ////////////////////////////////////////////// case object CurrentNode extends PathToken + sealed trait FilterValue extends AstToken object FilterDirectValue { - def long(value: Long) = FilterDirectValue(new LongNode(value)) - def double(value: Double) = FilterDirectValue(new DoubleNode(value)) - val True = FilterDirectValue(BooleanNode.TRUE) - val False = FilterDirectValue(BooleanNode.FALSE) - def string(value: String) = FilterDirectValue(new TextNode(value)) - val Null = FilterDirectValue(NullNode.instance) + def long(value: Long) = FilterDirectValue(JsNumber(BigDecimal(value))) + + def double(value: Double) = FilterDirectValue(JsNumber(BigDecimal(value))) + + val True = FilterDirectValue(JsBoolean(true)) + val False = FilterDirectValue(JsBoolean(false)) + + def string(value: String) = FilterDirectValue(JsString(value)) + + val Null = FilterDirectValue(JsNull) } - case class FilterDirectValue(node: JsonNode) extends FilterValue + case class FilterDirectValue(node: JsValue) extends FilterValue case class SubQuery(path: List[PathToken]) extends FilterValue @@ -70,4 +76,5 @@ object AST { case class BooleanFilter(operator: BinaryBooleanOperator, lhs: FilterToken, rhs: FilterToken) extends FilterToken case class RecursiveFilterToken(filter: FilterToken) extends PathToken + } diff --git a/src/main/scala/io/gatling/jsonpath/ComparisonOperators.scala b/src/main/scala/io/gatling/jsonpath/ComparisonOperators.scala index 3901d08..73f2473 100644 --- a/src/main/scala/io/gatling/jsonpath/ComparisonOperators.scala +++ b/src/main/scala/io/gatling/jsonpath/ComparisonOperators.scala @@ -16,12 +16,10 @@ package io.gatling.jsonpath -import com.fasterxml.jackson.core.JsonParser.NumberType -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.node.JsonNodeType._ +import play.api.libs.json.{ JsBoolean, JsNull, JsNumber, JsString, JsValue } sealed trait ComparisonOperator { - def apply(lhs: JsonNode, rhs: JsonNode): Boolean + def apply(lhs: JsValue, rhs: JsValue): Boolean } // Comparison operators @@ -29,79 +27,12 @@ sealed trait ComparisonWithOrderingOperator extends ComparisonOperator { protected def compare[T: Ordering](lhs: T, rhs: T): Boolean - def apply(lhs: JsonNode, rhs: JsonNode): Boolean = - lhs.getNodeType match { - case STRING => rhs.getNodeType == STRING && compare(lhs.textValue, rhs.textValue) - case BOOLEAN => rhs.getNodeType == BOOLEAN && compare(lhs.booleanValue, rhs.booleanValue) - case NUMBER => rhs.getNodeType match { - case NUMBER => - lhs.numberType match { - case NumberType.INT => - rhs.numberType match { - case NumberType.INT => compare(lhs.intValue, rhs.intValue) - case NumberType.LONG => compare(lhs.intValue, rhs.longValue) - case NumberType.DOUBLE => compare(lhs.intValue, rhs.doubleValue) - case NumberType.FLOAT => compare(lhs.intValue, rhs.floatValue) - case NumberType.BIG_INTEGER => compare(lhs.bigIntegerValue, rhs.bigIntegerValue) - case NumberType.BIG_DECIMAL => compare(lhs.decimalValue, rhs.decimalValue) - } - - case NumberType.LONG => - rhs.numberType match { - case NumberType.INT => compare(lhs.longValue, rhs.intValue) - case NumberType.LONG => compare(lhs.longValue, rhs.longValue) - case NumberType.DOUBLE => compare(lhs.longValue, rhs.doubleValue) - case NumberType.FLOAT => compare(lhs.longValue, rhs.floatValue) - case NumberType.BIG_INTEGER => compare(lhs.bigIntegerValue, rhs.bigIntegerValue) - case NumberType.BIG_DECIMAL => compare(lhs.decimalValue, rhs.decimalValue) - } - - case NumberType.FLOAT => - rhs.numberType match { - case NumberType.INT => compare(lhs.floatValue, rhs.intValue) - case NumberType.LONG => compare(lhs.floatValue, rhs.longValue) - case NumberType.DOUBLE => compare(lhs.floatValue, rhs.doubleValue) - case NumberType.FLOAT => compare(lhs.floatValue, rhs.floatValue) - case NumberType.BIG_INTEGER => compare(lhs.decimalValue, rhs.decimalValue) - case NumberType.BIG_DECIMAL => compare(lhs.decimalValue, rhs.decimalValue) - } - - case NumberType.DOUBLE => - rhs.numberType match { - case NumberType.INT => compare(lhs.doubleValue, rhs.intValue) - case NumberType.LONG => compare(lhs.doubleValue, rhs.longValue) - case NumberType.DOUBLE => compare(lhs.doubleValue, rhs.doubleValue) - case NumberType.FLOAT => compare(lhs.doubleValue, rhs.floatValue) - case NumberType.BIG_INTEGER => compare(lhs.decimalValue, rhs.decimalValue) - case NumberType.BIG_DECIMAL => compare(lhs.decimalValue, rhs.decimalValue) - } - - case NumberType.BIG_INTEGER => - rhs.numberType match { - case NumberType.INT => compare(lhs.bigIntegerValue, rhs.bigIntegerValue) - case NumberType.LONG => compare(lhs.bigIntegerValue, rhs.bigIntegerValue) - case NumberType.DOUBLE => compare(lhs.decimalValue, rhs.decimalValue) - case NumberType.FLOAT => compare(lhs.decimalValue, rhs.decimalValue) - case NumberType.BIG_INTEGER => compare(lhs.bigIntegerValue, rhs.bigIntegerValue) - case NumberType.BIG_DECIMAL => compare(lhs.decimalValue, rhs.decimalValue) - } - - case NumberType.BIG_DECIMAL => - rhs.numberType match { - case NumberType.INT => compare(lhs.decimalValue, rhs.decimalValue) - case NumberType.LONG => compare(lhs.decimalValue, rhs.decimalValue) - case NumberType.DOUBLE => compare(lhs.decimalValue, rhs.decimalValue) - case NumberType.FLOAT => compare(lhs.decimalValue, rhs.decimalValue) - case NumberType.BIG_INTEGER => compare(lhs.decimalValue, rhs.decimalValue) - case NumberType.BIG_DECIMAL => compare(lhs.decimalValue, rhs.decimalValue) - } - - case _ => false - } - case _ => false - } - - case _ => false + def apply(lhs: JsValue, rhs: JsValue): Boolean = + (lhs, rhs) match { + case (JsString(left), JsString(right)) => compare(left, right) + case (JsBoolean(left), JsBoolean(right)) => compare(left, right) + case (JsNumber(left), JsNumber(right)) => compare(left, right) + case _ => false } } @@ -110,12 +41,15 @@ case object EqWithOrderingOperator extends ComparisonWithOrderingOperator { } case object EqOperator extends ComparisonOperator { - override def apply(lhs: JsonNode, rhs: JsonNode): Boolean = - (lhs.getNodeType == NULL && rhs.getNodeType == NULL) || EqWithOrderingOperator(lhs, rhs) + override def apply(lhs: JsValue, rhs: JsValue): Boolean = + (lhs, rhs) match { + case (JsNull, JsNull) => true + case _ => EqWithOrderingOperator(lhs, rhs) + } } case object NotEqOperator extends ComparisonOperator { - override def apply(lhs: JsonNode, rhs: JsonNode): Boolean = !EqOperator(lhs, rhs) + override def apply(lhs: JsValue, rhs: JsValue): Boolean = !EqOperator(lhs, rhs) } case object LessOperator extends ComparisonWithOrderingOperator { diff --git a/src/main/scala/io/gatling/jsonpath/JsonPath.scala b/src/main/scala/io/gatling/jsonpath/JsonPath.scala index 4cb7ae2..261574c 100644 --- a/src/main/scala/io/gatling/jsonpath/JsonPath.scala +++ b/src/main/scala/io/gatling/jsonpath/JsonPath.scala @@ -16,99 +16,82 @@ package io.gatling.jsonpath -import scala.collection.JavaConverters._ import scala.math.abs import io.gatling.jsonpath.AST._ -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.node.JsonNodeType._ +import play.api.libs.json.{ JsArray, JsObject, JsValue } case class JPError(reason: String) object JsonPath { private val JsonPathParser = ThreadLocal.withInitial[Parser](() => new Parser) - def compile(query: String): Either[JPError, JsonPath] = + def compile(query: String): Either[JPError, JsonPathQuery] = JsonPathParser.get.compile(query) match { - case Parser.Success(q, _) => Right(new JsonPath(q)) + case Parser.Success(q, _) => Right(new JsonPathQuery(q)) case ns: Parser.NoSuccess => Left(JPError(ns.msg)) } - def query(query: String, jsonObject: JsonNode): Either[JPError, Iterator[JsonNode]] = + def query(query: String, jsonObject: JsValue): Either[JPError, Iterator[JsValue]] = compile(query).right.map(_.query(jsonObject)) } -class JsonPath(path: List[PathToken]) { - def query(jsonNode: JsonNode): Iterator[JsonNode] = new JsonPathWalker(jsonNode, path).walk() +class JsonPathQuery(path: List[PathToken]) extends Serializable { + def query(jsonNode: JsValue): Iterator[JsValue] = new JsonPathWalker(jsonNode, path).walk() } -class JsonPathWalker(rootNode: JsonNode, fullPath: List[PathToken]) { +class JsonPathWalker(rootNode: JsValue, fullPath: List[PathToken]) { - def walk(): Iterator[JsonNode] = walk(rootNode, fullPath) + def walk(): Iterator[JsValue] = walk(rootNode, fullPath) - private[this] def walk(node: JsonNode, path: List[PathToken]): Iterator[JsonNode] = + private[this] def walk(node: JsValue, path: List[PathToken]): Iterator[JsValue] = path match { case head :: tail => walk1(node, head).flatMap(walk(_, tail)) case _ => Iterator.single(node) } - private[this] def walk1(node: JsonNode, query: PathToken): Iterator[JsonNode] = + private[this] def walk1(node: JsValue, query: PathToken): Iterator[JsValue] = query match { case RootNode => Iterator.single(rootNode) case CurrentNode => Iterator.single(node) - case Field(name) => - if (node.getNodeType == OBJECT) { - val child = node.get(name) - if (child == null) { - Iterator.empty - } else { - Iterator.single(child) - } - } else { - Iterator.empty - } + case Field(name) => node match { + case obj: JsObject if obj.keys.contains(name) => + Iterator.single(obj.value(name)) + case _ => Iterator.empty + } case RecursiveField(name) => new RecursiveFieldIterator(node, name) - case MultiField(fieldNames) => - if (node.getNodeType == OBJECT) { + case MultiField(fieldNames) => node match { + case obj: JsObject => // don't use collect on iterator with filter causes (executed twice) - fieldNames.iterator.flatMap(name => Option(node.get(name))) - } else { - Iterator.empty - } + fieldNames.iterator.filter(obj.keys.contains).map(obj.value) + case _ => Iterator.empty + } - case AnyField => - if (node.getNodeType == OBJECT) { - node.elements.asScala - } else { - Iterator.empty - } + case AnyField => node match { + case obj: JsObject => obj.values.iterator + case _ => Iterator.empty + } - case ArraySlice(None, None, 1) => - if (node.getNodeType == ARRAY) { - node.elements.asScala - } else { - Iterator.empty - } + case ArraySlice(None, None, 1) => node match { + case array: JsArray => array.value.iterator + case _ => Iterator.empty + } - case ArraySlice(start, stop, step) => - if (node.getNodeType == ARRAY) { - sliceArray(node, start, stop, step) - } else { - Iterator.empty - } + case ArraySlice(start, stop, step) => node match { + case array: JsArray => sliceArray(array, start, stop, step) + case _ => Iterator.empty + } - case ArrayRandomAccess(indices) => - if (node.getNodeType == ARRAY) { - indices.iterator.collect { - case i if i >= 0 && i < node.size => node.get(i) - case i if i < 0 && i >= -node.size => node.get(i + node.size) - } - } else { - Iterator.empty + case ArrayRandomAccess(indices) => node match { + case array: JsArray => indices.iterator.collect { + case i if i >= 0 && i < array.value.size => array(i) + case i if i < 0 && i >= -array.value.size => array(i + array.value.size) } + case _ => Iterator.empty + } case RecursiveFilterToken(filterToken) => new RecursiveDataIterator(node).flatMap(applyFilter(_, filterToken)) @@ -117,33 +100,33 @@ class JsonPathWalker(rootNode: JsonNode, fullPath: List[PathToken]) { case RecursiveAnyField => new RecursiveNodeIterator(node) } - private[this] def applyFilter(currentNode: JsonNode, filterToken: FilterToken): Iterator[JsonNode] = { + private[this] def applyFilter(currentNode: JsValue, filterToken: FilterToken): Iterator[JsValue] = { - def resolveSubQuery(node: JsonNode, q: List[AST.PathToken], nextOp: JsonNode => Boolean): Boolean = { + def resolveSubQuery(node: JsValue, q: List[AST.PathToken], nextOp: JsValue => Boolean): Boolean = { val it = walk(node, q) it.hasNext && nextOp(it.next()) } - def applyBinaryOpWithResolvedLeft(node: JsonNode, op: ComparisonOperator, lhsNode: JsonNode, rhs: FilterValue): Boolean = + def applyBinaryOpWithResolvedLeft(node: JsValue, op: ComparisonOperator, lhsNode: JsValue, rhs: FilterValue): Boolean = rhs match { case FilterDirectValue(valueNode) => op(lhsNode, valueNode) case SubQuery(q) => resolveSubQuery(node, q, op(lhsNode, _)) } - def applyBinaryOp(op: ComparisonOperator, lhs: FilterValue, rhs: FilterValue): JsonNode => Boolean = + def applyBinaryOp(op: ComparisonOperator, lhs: FilterValue, rhs: FilterValue): JsValue => Boolean = lhs match { case FilterDirectValue(valueNode) => applyBinaryOpWithResolvedLeft(_, op, valueNode, rhs) case SubQuery(q) => node => resolveSubQuery(node, q, applyBinaryOpWithResolvedLeft(node, op, _, rhs)) } - def elementsToFilter(node: JsonNode): Iterator[JsonNode] = - node.getNodeType match { - case ARRAY => node.elements.asScala - case OBJECT => Iterator.single(node) - case _ => Iterator.empty + def elementsToFilter(node: JsValue): Iterator[JsValue] = + node match { + case array: JsArray => array.value.iterator + case obj: JsObject => Iterator.single(obj) + case _ => Iterator.empty } - def evaluateFilter(filterToken: FilterToken): JsonNode => Boolean = + def evaluateFilter(filterToken: FilterToken): JsValue => Boolean = filterToken match { case HasFilter(subQuery) => walk(_, subQuery.path).hasNext @@ -161,11 +144,13 @@ class JsonPathWalker(rootNode: JsonNode, fullPath: List[PathToken]) { elementsToFilter(currentNode).filter(filterFunction) } - private[this] def sliceArray(array: JsonNode, start: Option[Int], stop: Option[Int], step: Int): Iterator[JsonNode] = { - val size = array.size + private[this] def sliceArray(array: JsArray, start: Option[Int], stop: Option[Int], step: Int): Iterator[JsValue] = { + val size = array.value.size def lenRelative(x: Int) = if (x >= 0) x else size + x + def stepRelative(x: Int) = if (step >= 0) x else -1 - x + def relative(x: Int) = lenRelative(stepRelative(x)) val absStart = start match { @@ -178,7 +163,7 @@ class JsonPathWalker(rootNode: JsonNode, fullPath: List[PathToken]) { } val absStep = abs(step) - val elements: Iterator[JsonNode] = if (step < 0) Iterator.range(array.size - 1, -1, -1).map(array.get) else array.elements.asScala + val elements: Iterator[JsValue] = if (step < 0) Iterator.range(array.value.size - 1, -1, -1).map(array.value) else array.value.iterator val fromStartToEnd = elements.slice(absStart, absEnd) if (absStep == 1) diff --git a/src/main/scala/io/gatling/jsonpath/Parser.scala b/src/main/scala/io/gatling/jsonpath/Parser.scala index dd2960e..67c40fe 100644 --- a/src/main/scala/io/gatling/jsonpath/Parser.scala +++ b/src/main/scala/io/gatling/jsonpath/Parser.scala @@ -69,10 +69,15 @@ object Parser extends RegexParsers { private def field: Parser[String] = FieldRegex private def singleQuotedField = "'" ~> SingleQuotedFieldRegex <~ "'" ^^ (fastReplaceAll(_, "\\'", "'")) + private def doubleQuotedField = "\"" ~> DoubleQuotedFieldRegex <~ "\"" ^^ (fastReplaceAll(_, "\\\"", "\"")) + private def singleQuotedValue = "'" ~> SingleQuotedValueRegex <~ "'" ^^ (fastReplaceAll(_, "\\'", "'")) + private def doubleQuotedValue = "\"" ~> DoubleQuotedValueRegex <~ "\"" ^^ (fastReplaceAll(_, "\\\"", "\"")) + private def quotedField: Parser[String] = singleQuotedField | doubleQuotedField + private def quotedValue: Parser[String] = singleQuotedValue | doubleQuotedValue /// array parsers ///////////////////////////////////////////////////////// @@ -120,7 +125,10 @@ object Parser extends RegexParsers { private def nullValue: Parser[FilterValue] = "null" ^^^ FilterDirectValue.Null - private def stringValue: Parser[FilterDirectValue] = quotedValue ^^ { FilterDirectValue.string } + private def stringValue: Parser[FilterDirectValue] = quotedValue ^^ { + FilterDirectValue.string + } + private def value: Parser[FilterValue] = booleanValue | numberValue | nullValue | stringValue private def comparisonOperator: Parser[ComparisonOperator] = @@ -209,5 +217,6 @@ object Parser extends RegexParsers { class Parser { private val query = Parser.query + def compile(jsonpath: String): Parser.ParseResult[List[PathToken]] = Parser.parse(query, jsonpath) } diff --git a/src/main/scala/io/gatling/jsonpath/RecursiveDataIterator.scala b/src/main/scala/io/gatling/jsonpath/RecursiveDataIterator.scala index 2cc2887..85498a8 100644 --- a/src/main/scala/io/gatling/jsonpath/RecursiveDataIterator.scala +++ b/src/main/scala/io/gatling/jsonpath/RecursiveDataIterator.scala @@ -16,18 +16,16 @@ package io.gatling.jsonpath -import java.util.{ Iterator => JIterator } - -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.node.JsonNodeType.{ ARRAY, OBJECT } +import play.api.libs.json.{ JsArray, JsObject, JsValue } /** * Collect all nodes data (objects and leaves) + * * @param root the tree root */ -class RecursiveDataIterator(root: JsonNode) extends RecursiveIterator[JIterator[JsonNode]](root) { +class RecursiveDataIterator(root: JsValue) extends RecursiveIterator[Iterator[JsValue]](root) { - override protected def visit(it: JIterator[JsonNode]): Unit = { + override protected def visit(it: Iterator[JsValue]): Unit = { while (it.hasNext && !pause) { visitNode(it.next()) } @@ -36,19 +34,18 @@ class RecursiveDataIterator(root: JsonNode) extends RecursiveIterator[JIterator[ } } - override protected def visitNode(node: JsonNode): Unit = - node.getNodeType match { - case OBJECT => - if (node.size > 0) { + override protected def visitNode(node: JsValue): Unit = + node match { + case obj: JsObject => + if (obj.value.nonEmpty) { // only non empty objects - val it = node.elements + val it = obj.value.values.iterator stack = it :: stack nextNode = node pause = true } - - case ARRAY => - val it = node.elements + case array: JsArray => + val it = array.value.iterator stack = it :: stack visit(it) case _ => diff --git a/src/main/scala/io/gatling/jsonpath/RecursiveFieldIterator.scala b/src/main/scala/io/gatling/jsonpath/RecursiveFieldIterator.scala index a06e2f4..efe273f 100644 --- a/src/main/scala/io/gatling/jsonpath/RecursiveFieldIterator.scala +++ b/src/main/scala/io/gatling/jsonpath/RecursiveFieldIterator.scala @@ -16,41 +16,40 @@ package io.gatling.jsonpath -import java.util.{ Iterator => JIterator, Map => JMap } - -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.node.JsonNodeType.{ ARRAY, OBJECT } +import play.api.libs.json.{ JsArray, JsObject, JsValue } sealed trait VisitedIterator { - def hasNext(): Boolean + def hasNext: Boolean } -case class VisitedObject(it: JIterator[JMap.Entry[String, JsonNode]]) extends VisitedIterator { - override def hasNext(): Boolean = it.hasNext + +case class VisitedObject(it: Iterator[(String, JsValue)]) extends VisitedIterator { + override def hasNext: Boolean = it.hasNext } -case class VisitedArray(it: JIterator[JsonNode]) extends VisitedIterator { - override def hasNext(): Boolean = it.hasNext + +case class VisitedArray(it: Iterator[JsValue]) extends VisitedIterator { + override def hasNext: Boolean = it.hasNext } /** * Collect all first nodes in a branch with a given name + * * @param root the tree root * @param name the searched name */ -class RecursiveFieldIterator(root: JsonNode, name: String) extends RecursiveIterator[VisitedIterator](root) { +class RecursiveFieldIterator(root: JsValue, name: String) extends RecursiveIterator[VisitedIterator](root) { override def visit(t: VisitedIterator): Unit = t match { case VisitedObject(it) => visitObject(it) case VisitedArray(it) => visitArray(it) } - private def visitObject(it: JIterator[JMap.Entry[String, JsonNode]]): Unit = { + private def visitObject(it: Iterator[(String, JsValue)]): Unit = { while (it.hasNext && !pause) { - val e = it.next() - if (e.getKey == name) { - nextNode = e.getValue - pause = true - } else { - visitNode(e.getValue) + it.next() match { + case (key, value) if key == name => + nextNode = value + pause = true + case (_, value) => visitNode(value) } } if (!pause) { @@ -58,7 +57,7 @@ class RecursiveFieldIterator(root: JsonNode, name: String) extends RecursiveIter } } - private def visitArray(it: JIterator[JsonNode]): Unit = { + private def visitArray(it: Iterator[JsValue]): Unit = { while (it.hasNext && !pause) { visitNode(it.next()) } @@ -67,14 +66,14 @@ class RecursiveFieldIterator(root: JsonNode, name: String) extends RecursiveIter } } - protected def visitNode(node: JsonNode): Unit = - node.getNodeType match { - case OBJECT => - val it = node.fields + protected def visitNode(node: JsValue): Unit = + node match { + case obj: JsObject => + val it = obj.value.iterator stack = VisitedObject(it) :: stack visitObject(it) - case ARRAY => - val it = node.elements + case array: JsArray => + val it = array.value.iterator stack = VisitedArray(it) :: stack visitArray(it) case _ => diff --git a/src/main/scala/io/gatling/jsonpath/RecursiveIterator.scala b/src/main/scala/io/gatling/jsonpath/RecursiveIterator.scala index 2b1dbda..50e8fca 100644 --- a/src/main/scala/io/gatling/jsonpath/RecursiveIterator.scala +++ b/src/main/scala/io/gatling/jsonpath/RecursiveIterator.scala @@ -16,18 +16,18 @@ package io.gatling.jsonpath -import com.fasterxml.jackson.databind.JsonNode +import play.api.libs.json.JsValue import scala.collection.AbstractIterator -abstract class RecursiveIterator[T](root: JsonNode) extends AbstractIterator[JsonNode] { +abstract class RecursiveIterator[T](root: JsValue) extends AbstractIterator[JsValue] { - protected var nextNode: JsonNode = _ + protected var nextNode: JsValue = _ protected var finished: Boolean = _ protected var pause: Boolean = _ protected var stack: List[T] = _ - protected def visitNode(node: JsonNode): Unit + protected def visitNode(node: JsValue): Unit protected def visit(t: T): Unit @@ -49,7 +49,7 @@ abstract class RecursiveIterator[T](root: JsonNode) extends AbstractIterator[Jso !finished } - override def next(): JsonNode = + override def next(): JsValue = if (finished) { throw new UnsupportedOperationException("Can't call next on empty Iterator") } else if (nextNode == null) { diff --git a/src/main/scala/io/gatling/jsonpath/RecursiveNodeIterator.scala b/src/main/scala/io/gatling/jsonpath/RecursiveNodeIterator.scala index 1ef6ee0..bba2354 100644 --- a/src/main/scala/io/gatling/jsonpath/RecursiveNodeIterator.scala +++ b/src/main/scala/io/gatling/jsonpath/RecursiveNodeIterator.scala @@ -16,17 +16,16 @@ package io.gatling.jsonpath -import java.util.{ Iterator => JIterator } - -import com.fasterxml.jackson.databind.JsonNode +import play.api.libs.json.{ JsArray, JsObject, JsValue } /** * Collect all nodes + * * @param root the tree root */ -class RecursiveNodeIterator(root: JsonNode) extends RecursiveIterator[JIterator[JsonNode]](root) { +class RecursiveNodeIterator(root: JsValue) extends RecursiveIterator[Iterator[JsValue]](root) { - override protected def visit(it: JIterator[JsonNode]): Unit = { + override protected def visit(it: Iterator[JsValue]): Unit = { while (it.hasNext && !pause) { visitNode(it.next()) } @@ -35,11 +34,22 @@ class RecursiveNodeIterator(root: JsonNode) extends RecursiveIterator[JIterator[ } } - override protected def visitNode(node: JsonNode): Unit = { - if (node.size > 0) { - stack = node.elements :: stack + override protected def visitNode(node: JsValue): Unit = + node match { + case obj: JsObject => + if (obj.value.nonEmpty) { + stack = obj.value.values.iterator :: stack + } + nextNode = node + pause = true + case array: JsArray => + if (array.value.nonEmpty) { + stack = array.value.iterator :: stack + } + nextNode = node + pause = true + case _ => + nextNode = node + pause = true } - nextNode = node - pause = true - } } diff --git a/src/test/scala/io/gatling/jsonpath/ComparisonOperatorsSpec.scala b/src/test/scala/io/gatling/jsonpath/ComparisonOperatorsSpec.scala index 860293f..3d920d5 100644 --- a/src/test/scala/io/gatling/jsonpath/ComparisonOperatorsSpec.scala +++ b/src/test/scala/io/gatling/jsonpath/ComparisonOperatorsSpec.scala @@ -16,11 +16,11 @@ package io.gatling.jsonpath -import com.fasterxml.jackson.databind.node.{ BooleanNode, DoubleNode, FloatNode, IntNode, LongNode, TextNode } import org.scalacheck.Arbitrary import org.scalacheck.Arbitrary.arbitrary import org.scalatest.prop.GeneratorDrivenPropertyChecks import org.scalatest.{ FlatSpec, Matchers } +import play.api.libs.json.{ JsBoolean, JsNumber, JsString } class ComparisonOperatorsSpec extends FlatSpec @@ -29,8 +29,8 @@ class ComparisonOperatorsSpec "comparison operators" should "return false if types aren't compatible" in { forAll(arbitrary[String], arbitrary[Int]) { (string, int) => - val lhn = new TextNode(string) - val rhn = new IntNode(int) + val lhn = JsString(string) + val rhn = JsNumber(BigDecimal(int)) LessOperator(lhn, rhn) shouldBe false GreaterOperator(lhn, rhn) shouldBe false LessOrEqOperator(lhn, rhn) shouldBe false @@ -38,8 +38,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Boolean], arbitrary[String]) { (bool, string) => - val lhn = BooleanNode.valueOf(bool) - val rhn = new TextNode(string) + val lhn = JsBoolean(bool) + val rhn = JsString(string) LessOperator(lhn, rhn) shouldBe false GreaterOperator(lhn, rhn) shouldBe false LessOrEqOperator(lhn, rhn) shouldBe false @@ -47,8 +47,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Int], arbitrary[String]) { (int, string) => - val lhn = new IntNode(int) - val rhn = new TextNode(string) + val lhn = JsNumber(BigDecimal(int)) + val rhn = JsString(string) LessOperator(lhn, rhn) shouldBe false GreaterOperator(lhn, rhn) shouldBe false LessOrEqOperator(lhn, rhn) shouldBe false @@ -58,8 +58,8 @@ class ComparisonOperatorsSpec it should "properly compare Strings" in { forAll(arbitrary[String], arbitrary[String]) { (val1, val2) => - val lhn = new TextNode(val1) - val rhn = new TextNode(val2) + val lhn = JsString(val1) + val rhn = JsString(val2) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -69,8 +69,8 @@ class ComparisonOperatorsSpec it should "properly compare Booleans" in { forAll(arbitrary[Boolean], arbitrary[Boolean]) { (val1, val2) => - val lhn = BooleanNode.valueOf(val1) - val rhn = BooleanNode.valueOf(val2) + val lhn = JsBoolean(val1) + val rhn = JsBoolean(val2) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -80,8 +80,8 @@ class ComparisonOperatorsSpec it should "properly compare Int with other numeric types" in { forAll(arbitrary[Int], arbitrary[Int]) { (val1, val2) => - val lhn = new IntNode(val1) - val rhn = new IntNode(val2) + val lhn = JsNumber(BigDecimal(val1)) + val rhn = JsNumber(BigDecimal(val2)) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -89,8 +89,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Int], arbitrary[Long]) { (val1, val2) => - val lhn = new IntNode(val1) - val rhn = new LongNode(val2) + val lhn = JsNumber(BigDecimal(val1)) + val rhn = JsNumber(BigDecimal(val2)) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -98,8 +98,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Int], arbitrary[Double]) { (val1, val2) => - val lhn = new IntNode(val1) - val rhn = new DoubleNode(val2) + val lhn = JsNumber(BigDecimal(val1)) + val rhn = JsNumber(BigDecimal(val2)) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -107,8 +107,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Int], arbitrary[Float]) { (val1, val2) => - val lhn = new IntNode(val1) - val rhn = new FloatNode(val2) + val lhn = JsNumber(BigDecimal(val1)) + val rhn = JsNumber(BigDecimal(val2)) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -118,8 +118,8 @@ class ComparisonOperatorsSpec it should "properly compare Long with other numeric types" in { forAll(arbitrary[Long], arbitrary[Int]) { (val1, val2) => - val lhn = new LongNode(val1) - val rhn = new IntNode(val2) + val lhn = JsNumber(BigDecimal(val1)) + val rhn = JsNumber(BigDecimal(val2)) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -127,8 +127,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Long], arbitrary[Long]) { (val1, val2) => - val lhn = new LongNode(val1) - val rhn = new LongNode(val2) + val lhn = JsNumber(BigDecimal(val1)) + val rhn = JsNumber(BigDecimal(val2)) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -136,8 +136,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Long], arbitrary[Double]) { (val1, val2) => - val lhn = new LongNode(val1) - val rhn = new DoubleNode(val2) + val lhn = JsNumber(BigDecimal(val1)) + val rhn = JsNumber(BigDecimal(val2)) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -145,8 +145,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Long], arbitrary[Float]) { (val1, val2) => - val lhn = new LongNode(val1) - val rhn = new FloatNode(val2) + val lhn = JsNumber(BigDecimal(val1)) + val rhn = JsNumber(BigDecimal(val2)) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -156,8 +156,8 @@ class ComparisonOperatorsSpec it should "properly compare Double with other numeric types" in { forAll(arbitrary[Double], arbitrary[Int]) { (val1, val2) => - val lhn = new DoubleNode(val1) - val rhn = new IntNode(val2) + val lhn = JsNumber(BigDecimal(val1)) + val rhn = JsNumber(BigDecimal(val2)) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -165,8 +165,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Double], arbitrary[Long]) { (val1, val2) => - val lhn = new DoubleNode(val1) - val rhn = new LongNode(val2) + val lhn = JsNumber(BigDecimal(val1)) + val rhn = JsNumber(BigDecimal(val2)) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -174,8 +174,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Double], arbitrary[Double]) { (val1, val2) => - val lhn = new DoubleNode(val1) - val rhn = new DoubleNode(val2) + val lhn = JsNumber(BigDecimal(val1)) + val rhn = JsNumber(BigDecimal(val2)) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -183,8 +183,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Double], arbitrary[Float]) { (val1, val2) => - val lhn = new DoubleNode(val1) - val rhn = new FloatNode(val2) + val lhn = JsNumber(BigDecimal(val1)) + val rhn = JsNumber(BigDecimal(val2)) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -194,8 +194,8 @@ class ComparisonOperatorsSpec it should "properly compare Float with other numeric types" in { forAll(arbitrary[Float], arbitrary[Int]) { (val1, val2) => - val lhn = new FloatNode(val1) - val rhn = new IntNode(val2) + val lhn = JsNumber(BigDecimal(val1)) + val rhn = JsNumber(BigDecimal(val2)) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -203,8 +203,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Float], arbitrary[Long]) { (val1, val2) => - val lhn = new FloatNode(val1) - val rhn = new LongNode(val2) + val lhn = JsNumber(BigDecimal(val1)) + val rhn = JsNumber(BigDecimal(val2)) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -212,8 +212,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Float], arbitrary[Double]) { (val1, val2) => - val lhn = new FloatNode(val1) - val rhn = new DoubleNode(val2) + val lhn = JsNumber(BigDecimal(val1)) + val rhn = JsNumber(BigDecimal(val2)) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -221,8 +221,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Float], arbitrary[Float]) { (val1, val2) => - val lhn = new FloatNode(val1) - val rhn = new FloatNode(val2) + val lhn = JsNumber(BigDecimal(val1)) + val rhn = JsNumber(BigDecimal(val2)) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) diff --git a/src/test/scala/io/gatling/jsonpath/JsonPathSpec.scala b/src/test/scala/io/gatling/jsonpath/JsonPathSpec.scala index 63511fc..b06bf91 100644 --- a/src/test/scala/io/gatling/jsonpath/JsonPathSpec.scala +++ b/src/test/scala/io/gatling/jsonpath/JsonPathSpec.scala @@ -16,786 +16,775 @@ package io.gatling.jsonpath -import java.util.{ HashMap => JHashMap, List => JList } - -import com.fasterxml.jackson.databind.node.{ BooleanNode, DoubleNode, IntNode, NullNode, ObjectNode, TextNode } - -import scala.collection.JavaConverters._ import org.scalatest.{ FlatSpec, Matchers } import org.scalatest.matchers.{ MatchResult, Matcher } -import com.fasterxml.jackson.databind.{ JsonNode, ObjectMapper } +import play.api.libs.json.{ JsBoolean, JsNull, JsNumber, JsObject, JsString, JsValue, Json } class JsonPathSpec extends FlatSpec with Matchers with JsonPathMatchers { - val mapper = new ObjectMapper - - def parseJson(s: String) = mapper.readValue(s, classOf[JsonNode]) - def bool(b: Boolean) = BooleanNode.valueOf(b) - def int(i: Int) = IntNode.valueOf(i) - def double(f: Double) = DoubleNode.valueOf(f) - def text(s: String) = TextNode.valueOf(s) - def nullNode: Any = NullNode.instance - // def array(elts: Any*): JList[Any] = elts.asJava - // def obj(elts: (String, Any)*) = { - // val node = new ObjectNode - // elts.foreach { case (key, value) => - // node.put(key, value) - // } - // node - // } - - // Goessner JSON exemple - - val book1 = """{"category":"reference","author":"Nigel Rees","title":"Sayings of the Century","price":8.95}""" - val book2 = """{"category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99}""" - val book3 = """{"category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99}""" - val book4 = """{"category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":22.99}""" - val allBooks = s"[$book1,$book2,$book3,$book4]" - val bicycle = s"""{"color":"red","price":19.95}""" - val allStore = s"""{"book":$allBooks, "bicycle":$bicycle}""" - val goessnerData = s"""{"store":$allStore}""" - val goessnerJson = parseJson(goessnerData) - - val json = """[ - | { - | "id":19434, - | "foo":1, - | "company": - | { - | "id":18971 - | }, - | "owner": - | { - | "id":18957 - | }, - | "process": - | { - | "id":18972 - | } - | }, - | { - | "id":19435, - | "foo":2, - | "company": - | { - | "id":18972 - | }, - | "owner": - | { - | "id":18957 - | }, - | "process": - | { - | "id":18974 - | } - | } - |]""".stripMargin - - val json2 = """[ - | { - | "unit": "flow.flow-server.aaa.gatlinguser202project.gatlinguser202.service", - | "machine": "f374e9a176b341d4a8ee8db3cdfb2958/10.11.12.124", - | "active": "active", - | "sub": "running", - | "space": "aaa.gatlinguser202project.gatlinguser202", - | "@timestamp": 1418285656070 - | }, - | { - | "unit": "XXXXXXXflow.flow-server.aaa.gatlinguser202project.gatlinguser202.service", - | "machine": "f374e9a176b341d4a8ee8db3cdfb2958/10.11.12.124", - | "active": "active", - | "sub": "running", - | "space": "aaa.gatlinguser202project.gatlinguser202", - | "@timestamp": 1418285656070 - | } - |]""".stripMargin - - private val veggies = """[ - | { - | "vegetable": { - | "name": "peas", - | "color": "green" - | }, - | "meet": { - | "name":"beef", - | "color":"red" - | } - | }, - | { - | "vegetable": { - | "name": "potato", - | "color": "yellow" - | }, - | "meet": { - | "name":"lamb", - | "color":"brown" - | } - | } - |]""".stripMargin - - val searches = """[ - | { - | "changes": [ - | [ - | "change", - | { - | "pid": "520" - | }, - | [ - | "0", - | { - | "id": "520", - | "location": "foo", - | "v": { - | "action": "" - | } - | }, - | [ - | "actions", - | { - | - | }, - | [ - | "action", - | { - | "key": "1", - | "kc": 81, - | "mk": [ - | "18", - | "16", - | "17" - | ] - | } - | ], - | [ - | "action", - | { - | "key": "2", - | "kc": 13, - | "mk": [ - | - | ] - | } - | ], - | [ - | "action", - | { - | "key": "3", - | "kc": 13, - | "mk": [ - | - | ] - | } - | ] - | ] - | ] - | ], - | [ - | "change", - | { - | "pid": "1012" - | }, - | [ - | "23", - | { - | "id": "1012", - | "multiselectmode": 1, - | "selectmode": "multi", - | "cols": 13, - | "rows": 19, - | "firstrow": 0, - | "totalrows": 20, - | "pagelength": 19, - | "colheaders": true, - | "colfooters": false, - | "vcolorder": [ - | "1", - | "2", - | "3", - | "4", - | "5", - | "6", - | "7", - | "8", - | "9", - | "10", - | "11", - | "12", - | "13" - | ], - | "pb-ft": 0, - | "pb-l": 18, - | "clearKeyMap": true, - | "v": { - | "selected": [ - | - | ], - | "firstvisible": 0, - | "sortcolumn": "null", - | "sortascending": true, - | "reqrows": -1, - | "reqfirstrow": -1, - | "columnorder": [ - | "1", - | "2", - | "3", - | "4", - | "5", - | "6", - | "7", - | "8", - | "9", - | "10", - | "11", - | "12", - | "13" - | ], - | "collapsedcolumns": [ - | - | ], - | "noncollapsiblecolumns": [ - | "1" - | ] - | } - | }, - | [ - | "rows", - | { - | - | }, - | [ - | "tr", - | { - | "key": 191, - | "style-4": "perfectMatch" - | }, - | "" - | ], - | [ - | "tr", - | { - | "key": 192, - | "style-5": "perfectMatch", - | "style-10": "perfectMatch" - | }, - | "" - | ], - | [ - | "tr", - | { - | "key": 193, - | "style-4": "perfectMatch", - | "style-5": "perfectMatch", - | "style-10": "perfectMatch" - | }, - | "" - | ], - | [ - | "tr", - | { - | "key": 194, - | "style-4": "perfectMatch", - | "style-10": "perfectMatch" - | }, - | "" - | ], - | [ - | "tr", - | { - | "key": 195, - | "style-4": "perfectMatch" - | }, - | "" - | ], - | [ - | "tr", - | { - | "key": 196 - | }, - | "" - | ], - | [ - | "tr", - | { - | "key": 197 - | }, - | "" - | ], - | [ - | "tr", - | { - | "key": 198 - | }, - | "" - | ], - | [ - | "tr", - | { - | "key": 199 - | }, - | "" - | ], - | [ - | "tr", - | { - | "key": 200 - | }, - | "" - | ], - | [ - | "tr", - | { - | "key": 201 - | }, - | "" - | ], - | [ - | "tr", - | { - | "key": 202 - | }, - | "" - | ], - | [ - | "tr", - | { - | "key": 203 - | }, - | "" - | ], - | [ - | "tr", - | { - | "key": 204, - | "style-4": "perfectMatch", - | "style-10": "perfectMatch" - | }, - | "" - | ], - | [ - | "tr", - | { - | "key": 205, - | "style-4": "perfectMatch", - | "style-10": "perfectMatch" - | }, - | "" - | ], - | [ - | "tr", - | { - | "key": 206, - | "style-4": "perfectMatch", - | "style-10": "perfectMatch" - | }, - | "" - | ], - | [ - | "tr", - | { - | "key": 207, - | "style-4": "perfectMatch", - | "style-10": "perfectMatch" - | }, - | "" - | ], - | [ - | "tr", - | { - | "key": 208, - | "style-4": "perfectMatch", - | "style-10": "perfectMatch" - | }, - | "" - | ], - | [ - | "tr", - | { - | "key": 209 - | }, - | "" - | ] - | ], - | [ - | "visiblecolumns", - | { - | - | }, - | [ - | "column", - | { - | "cid": "1", - | "caption": "foo", - | "fcaption": "", - | "sortable": true - | } - | ], - | [ - | "column", - | { - | "cid": "2", - | "caption": "bar", - | "fcaption": "", - | "sortable": true - | } - | ], - | [ - | "column", - | { - | "cid": "3", - | "caption": "baz", - | "fcaption": "", - | "sortable": true - | } - | ], - | [ - | "column", - | { - | "cid": "4", - | "caption": "too", - | "fcaption": "", - | "sortable": true - | } - | ], - | [ - | "column", - | { - | "cid": "5", - | "caption": "xxx", - | "fcaption": "", - | "sortable": true - | } - | ], - | [ - | "column", - | { - | "cid": "6", - | "caption": "yyy", - | "fcaption": "", - | "sortable": true - | } - | ], - | [ - | "column", - | { - | "cid": "7", - | "caption": "zzz", - | "fcaption": "", - | "sortable": true - | } - | ], - | [ - | "column", - | { - | "cid": "8", - | "caption": "aaa", - | "fcaption": "", - | "sortable": true - | } - | ], - | [ - | "column", - | { - | "cid": "9", - | "caption": "bbb", - | "fcaption": "", - | "sortable": true - | } - | ], - | [ - | "column", - | { - | "cid": "10", - | "caption": "ccc", - | "fcaption": "", - | "sortable": true - | } - | ], - | [ - | "column", - | { - | "cid": "11", - | "caption": "ddd2", - | "fcaption": "", - | "sortable": true - | } - | ], - | [ - | "column", - | { - | "cid": "12", - | "caption": "eee", - | "fcaption": "", - | "sortable": true - | } - | ], - | [ - | "column", - | { - | "cid": "13", - | "caption": "fff", - | "fcaption": "", - | "sortable": true - | } - | ] - | ] - | ] - | ], - | [ - | "change", - | { - | "pid": "997" - | }, - | [ - | "1", - | { - | "id": "997" - | } - | ] - | ], - | [ - | "change", - | { - | "pid": "1011" - | }, - | [ - | "1", - | { - | "id": "1011" - | } - | ] - | ], - | [ - | "change", - | { - | "pid": "996" - | }, - | [ - | "1", - | { - | "id": "996" - | } - | ] - | ], - | [ - | "change", - | { - | "pid": "994" - | }, - | [ - | "1", - | { - | "id": "994" - | } - | ] - | ], - | [ - | "change", - | { - | "pid": "999" - | }, - | [ - | "1", - | { - | "id": "999" - | } - | ] - | ], - | [ - | "change", - | { - | "pid": "993" - | }, - | [ - | "1", - | { - | "id": "993" - | } - | ] - | ], - | [ - | "change", - | { - | "pid": "995" - | }, - | [ - | "1", - | { - | "id": "995" - | } - | ] - | ], - | [ - | "change", - | { - | "pid": "1003" - | }, - | [ - | "1", - | { - | "id": "1003" - | } - | ] - | ], - | [ - | "change", - | { - | "pid": "990" - | }, - | [ - | "1", - | { - | "id": "990" - | } - | ] - | ], - | [ - | "change", - | { - | "pid": "992" - | }, - | [ - | "1", - | { - | "id": "992" - | } - | ] - | ], - | [ - | "change", - | { - | "pid": "1005" - | }, - | [ - | "1", - | { - | "id": "1005" - | } - | ] - | ], - | [ - | "change", - | { - | "pid": "1001" - | }, - | [ - | "1", - | { - | "id": "1001" - | } - | ] - | ], - | [ - | "change", - | { - | "pid": "991" - | }, - | [ - | "1", - | { - | "id": "991" - | } - | ] - | ] - | ], - | "state": { - | "520": { - | "pollInterval": -1 - | }, - | "1011": { - | "text": "(20)" - | } - | }, - | "types": { - | "520": "0", - | "990": "1", - | "991": "1", - | "992": "1", - | "993": "1", - | "994": "1", - | "995": "1", - | "996": "1", - | "997": "1", - | "999": "1", - | "1001": "1", - | "1003": "1", - | "1005": "1", - | "1011": "1", - | "1012": "23" - | }, - | "hierarchy": { - | "520": [ - | "983", - | "521", - | "984" - | ], - | "990": [ - | - | ], - | "991": [ - | - | ], - | "992": [ - | - | ], - | "993": [ - | - | ], - | "994": [ - | - | ], - | "995": [ - | - | ], - | "996": [ - | - | ], - | "997": [ - | - | ], - | "999": [ - | - | ], - | "1001": [ - | - | ], - | "1003": [ - | - | ], - | "1005": [ - | - | ], - | "1011": [ - | - | ], - | "1012": [ - | - | ] - | }, - | "rpc": [ - | - | ], - | "meta": { - | - | }, - | "resources": { - | - | }, - | "timings": [ - | 4509, - | 1 - | ] - | } - |]""".stripMargin - - val valuesWithParensAndBraces = + def parseJson(s: String): JsValue = Json.parse(s) + def bool(b: Boolean): JsValue = JsBoolean(b) + def int(i: Int): JsValue = JsNumber(BigDecimal(i)) + def double(f: Double): JsValue = JsNumber(BigDecimal(f)) + def text(s: String): JsValue = JsString(s) + def nullNode: JsValue = JsNull + + // Goessner JSON example + + val book1: String = """{"category":"reference","author":"Nigel Rees","title":"Sayings of the Century","price":8.95}""" + val book2: String = """{"category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99}""" + val book3: String = """{"category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99}""" + val book4: String = """{"category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":22.99}""" + val allBooks: String = s"[$book1,$book2,$book3,$book4]" + val bicycle: String = s"""{"color":"red","price":19.95}""" + val allStore: String = s"""{"book":$allBooks, "bicycle":$bicycle}""" + val goessnerData: String = s"""{"store":$allStore}""" + val goessnerJson: JsValue = parseJson(goessnerData) + + val json: String = + """[ + | { + | "id":19434, + | "foo":1, + | "company": + | { + | "id":18971 + | }, + | "owner": + | { + | "id":18957 + | }, + | "process": + | { + | "id":18972 + | } + | }, + | { + | "id":19435, + | "foo":2, + | "company": + | { + | "id":18972 + | }, + | "owner": + | { + | "id":18957 + | }, + | "process": + | { + | "id":18974 + | } + | } + |]""".stripMargin + + val json2: String = + """[ + | { + | "unit": "flow.flow-server.aaa.gatlinguser202project.gatlinguser202.service", + | "machine": "f374e9a176b341d4a8ee8db3cdfb2958/10.11.12.124", + | "active": "active", + | "sub": "running", + | "space": "aaa.gatlinguser202project.gatlinguser202", + | "@timestamp": 1418285656070 + | }, + | { + | "unit": "XXXXXXXflow.flow-server.aaa.gatlinguser202project.gatlinguser202.service", + | "machine": "f374e9a176b341d4a8ee8db3cdfb2958/10.11.12.124", + | "active": "active", + | "sub": "running", + | "space": "aaa.gatlinguser202project.gatlinguser202", + | "@timestamp": 1418285656070 + | } + |]""".stripMargin + + val veggies: String = + """[ + | { + | "vegetable": { + | "name": "peas", + | "color": "green" + | }, + | "meet": { + | "name":"beef", + | "color":"red" + | } + | }, + | { + | "vegetable": { + | "name": "potato", + | "color": "yellow" + | }, + | "meet": { + | "name":"lamb", + | "color":"brown" + | } + | } + |]""".stripMargin + + val searches: String = + """[ + | { + | "changes": [ + | [ + | "change", + | { + | "pid": "520" + | }, + | [ + | "0", + | { + | "id": "520", + | "location": "foo", + | "v": { + | "action": "" + | } + | }, + | [ + | "actions", + | { + | + | }, + | [ + | "action", + | { + | "key": "1", + | "kc": 81, + | "mk": [ + | "18", + | "16", + | "17" + | ] + | } + | ], + | [ + | "action", + | { + | "key": "2", + | "kc": 13, + | "mk": [ + | + | ] + | } + | ], + | [ + | "action", + | { + | "key": "3", + | "kc": 13, + | "mk": [ + | + | ] + | } + | ] + | ] + | ] + | ], + | [ + | "change", + | { + | "pid": "1012" + | }, + | [ + | "23", + | { + | "id": "1012", + | "multiselectmode": 1, + | "selectmode": "multi", + | "cols": 13, + | "rows": 19, + | "firstrow": 0, + | "totalrows": 20, + | "pagelength": 19, + | "colheaders": true, + | "colfooters": false, + | "vcolorder": [ + | "1", + | "2", + | "3", + | "4", + | "5", + | "6", + | "7", + | "8", + | "9", + | "10", + | "11", + | "12", + | "13" + | ], + | "pb-ft": 0, + | "pb-l": 18, + | "clearKeyMap": true, + | "v": { + | "selected": [ + | + | ], + | "firstvisible": 0, + | "sortcolumn": "null", + | "sortascending": true, + | "reqrows": -1, + | "reqfirstrow": -1, + | "columnorder": [ + | "1", + | "2", + | "3", + | "4", + | "5", + | "6", + | "7", + | "8", + | "9", + | "10", + | "11", + | "12", + | "13" + | ], + | "collapsedcolumns": [ + | + | ], + | "noncollapsiblecolumns": [ + | "1" + | ] + | } + | }, + | [ + | "rows", + | { + | + | }, + | [ + | "tr", + | { + | "key": 191, + | "style-4": "perfectMatch" + | }, + | "" + | ], + | [ + | "tr", + | { + | "key": 192, + | "style-5": "perfectMatch", + | "style-10": "perfectMatch" + | }, + | "" + | ], + | [ + | "tr", + | { + | "key": 193, + | "style-4": "perfectMatch", + | "style-5": "perfectMatch", + | "style-10": "perfectMatch" + | }, + | "" + | ], + | [ + | "tr", + | { + | "key": 194, + | "style-4": "perfectMatch", + | "style-10": "perfectMatch" + | }, + | "" + | ], + | [ + | "tr", + | { + | "key": 195, + | "style-4": "perfectMatch" + | }, + | "" + | ], + | [ + | "tr", + | { + | "key": 196 + | }, + | "" + | ], + | [ + | "tr", + | { + | "key": 197 + | }, + | "" + | ], + | [ + | "tr", + | { + | "key": 198 + | }, + | "" + | ], + | [ + | "tr", + | { + | "key": 199 + | }, + | "" + | ], + | [ + | "tr", + | { + | "key": 200 + | }, + | "" + | ], + | [ + | "tr", + | { + | "key": 201 + | }, + | "" + | ], + | [ + | "tr", + | { + | "key": 202 + | }, + | "" + | ], + | [ + | "tr", + | { + | "key": 203 + | }, + | "" + | ], + | [ + | "tr", + | { + | "key": 204, + | "style-4": "perfectMatch", + | "style-10": "perfectMatch" + | }, + | "" + | ], + | [ + | "tr", + | { + | "key": 205, + | "style-4": "perfectMatch", + | "style-10": "perfectMatch" + | }, + | "" + | ], + | [ + | "tr", + | { + | "key": 206, + | "style-4": "perfectMatch", + | "style-10": "perfectMatch" + | }, + | "" + | ], + | [ + | "tr", + | { + | "key": 207, + | "style-4": "perfectMatch", + | "style-10": "perfectMatch" + | }, + | "" + | ], + | [ + | "tr", + | { + | "key": 208, + | "style-4": "perfectMatch", + | "style-10": "perfectMatch" + | }, + | "" + | ], + | [ + | "tr", + | { + | "key": 209 + | }, + | "" + | ] + | ], + | [ + | "visiblecolumns", + | { + | + | }, + | [ + | "column", + | { + | "cid": "1", + | "caption": "foo", + | "fcaption": "", + | "sortable": true + | } + | ], + | [ + | "column", + | { + | "cid": "2", + | "caption": "bar", + | "fcaption": "", + | "sortable": true + | } + | ], + | [ + | "column", + | { + | "cid": "3", + | "caption": "baz", + | "fcaption": "", + | "sortable": true + | } + | ], + | [ + | "column", + | { + | "cid": "4", + | "caption": "too", + | "fcaption": "", + | "sortable": true + | } + | ], + | [ + | "column", + | { + | "cid": "5", + | "caption": "xxx", + | "fcaption": "", + | "sortable": true + | } + | ], + | [ + | "column", + | { + | "cid": "6", + | "caption": "yyy", + | "fcaption": "", + | "sortable": true + | } + | ], + | [ + | "column", + | { + | "cid": "7", + | "caption": "zzz", + | "fcaption": "", + | "sortable": true + | } + | ], + | [ + | "column", + | { + | "cid": "8", + | "caption": "aaa", + | "fcaption": "", + | "sortable": true + | } + | ], + | [ + | "column", + | { + | "cid": "9", + | "caption": "bbb", + | "fcaption": "", + | "sortable": true + | } + | ], + | [ + | "column", + | { + | "cid": "10", + | "caption": "ccc", + | "fcaption": "", + | "sortable": true + | } + | ], + | [ + | "column", + | { + | "cid": "11", + | "caption": "ddd2", + | "fcaption": "", + | "sortable": true + | } + | ], + | [ + | "column", + | { + | "cid": "12", + | "caption": "eee", + | "fcaption": "", + | "sortable": true + | } + | ], + | [ + | "column", + | { + | "cid": "13", + | "caption": "fff", + | "fcaption": "", + | "sortable": true + | } + | ] + | ] + | ] + | ], + | [ + | "change", + | { + | "pid": "997" + | }, + | [ + | "1", + | { + | "id": "997" + | } + | ] + | ], + | [ + | "change", + | { + | "pid": "1011" + | }, + | [ + | "1", + | { + | "id": "1011" + | } + | ] + | ], + | [ + | "change", + | { + | "pid": "996" + | }, + | [ + | "1", + | { + | "id": "996" + | } + | ] + | ], + | [ + | "change", + | { + | "pid": "994" + | }, + | [ + | "1", + | { + | "id": "994" + | } + | ] + | ], + | [ + | "change", + | { + | "pid": "999" + | }, + | [ + | "1", + | { + | "id": "999" + | } + | ] + | ], + | [ + | "change", + | { + | "pid": "993" + | }, + | [ + | "1", + | { + | "id": "993" + | } + | ] + | ], + | [ + | "change", + | { + | "pid": "995" + | }, + | [ + | "1", + | { + | "id": "995" + | } + | ] + | ], + | [ + | "change", + | { + | "pid": "1003" + | }, + | [ + | "1", + | { + | "id": "1003" + | } + | ] + | ], + | [ + | "change", + | { + | "pid": "990" + | }, + | [ + | "1", + | { + | "id": "990" + | } + | ] + | ], + | [ + | "change", + | { + | "pid": "992" + | }, + | [ + | "1", + | { + | "id": "992" + | } + | ] + | ], + | [ + | "change", + | { + | "pid": "1005" + | }, + | [ + | "1", + | { + | "id": "1005" + | } + | ] + | ], + | [ + | "change", + | { + | "pid": "1001" + | }, + | [ + | "1", + | { + | "id": "1001" + | } + | ] + | ], + | [ + | "change", + | { + | "pid": "991" + | }, + | [ + | "1", + | { + | "id": "991" + | } + | ] + | ] + | ], + | "state": { + | "520": { + | "pollInterval": -1 + | }, + | "1011": { + | "text": "(20)" + | } + | }, + | "types": { + | "520": "0", + | "990": "1", + | "991": "1", + | "992": "1", + | "993": "1", + | "994": "1", + | "995": "1", + | "996": "1", + | "997": "1", + | "999": "1", + | "1001": "1", + | "1003": "1", + | "1005": "1", + | "1011": "1", + | "1012": "23" + | }, + | "hierarchy": { + | "520": [ + | "983", + | "521", + | "984" + | ], + | "990": [ + | + | ], + | "991": [ + | + | ], + | "992": [ + | + | ], + | "993": [ + | + | ], + | "994": [ + | + | ], + | "995": [ + | + | ], + | "996": [ + | + | ], + | "997": [ + | + | ], + | "999": [ + | + | ], + | "1001": [ + | + | ], + | "1003": [ + | + | ], + | "1005": [ + | + | ], + | "1011": [ + | + | ], + | "1012": [ + | + | ] + | }, + | "rpc": [ + | + | ], + | "meta": { + | + | }, + | "resources": { + | + | }, + | "timings": [ + | 4509, + | 1 + | ] + | } + |]""".stripMargin + + val valuesWithParensAndBraces: String = """{ - | "error": { - | "id": 1, - | "message1": "bar(baz)", - | "message2": "bar[baz]" - | } - |}""".stripMargin + | "error": { + | "id": 1, + | "message1": "bar(baz)", + | "message2": "bar[baz]" + | } + |}""".stripMargin ////////////// @@ -831,7 +820,8 @@ class JsonPathSpec extends FlatSpec with Matchers with JsonPathMatchers { } it should "work with test set 3" in { - val json = parseJson("""{ "points": [ + val json = parseJson( + """{ "points": [ { "id":"i1", "x": 4, "y":-5 }, { "id":"i2", "x":-2, "y": 2, "z":1 }, { "id":"i3", "x": 8, "y": 3 }, @@ -839,7 +829,8 @@ class JsonPathSpec extends FlatSpec with Matchers with JsonPathMatchers { { "id":"i5", "x": 0, "y": 2, "z":1 }, { "id":"i6", "x": 1, "y": 4 } ] - }""") + }""" + ) JsonPath.query("$.points[1]", json) should findOrderedElements(parseJson("""{ "id":"i2", "x":-2, "y": 2, "z":1 }""")) JsonPath.query("$.points[4].x", json) should findOrderedElements(int(0)) @@ -852,9 +843,11 @@ class JsonPathSpec extends FlatSpec with Matchers with JsonPathMatchers { } it should "work with boolean filters" in { - val json = parseJson("""{ "conditions": + val json = parseJson( + """{ "conditions": [true, false, true] - }""") + }""" + ) JsonPath.query("$.conditions[?(@ == true)]", json) should findElements(bool(true), bool(true)) JsonPath.query("$.conditions[?(@ == false)]", json) should findElements(bool(false)) @@ -862,12 +855,14 @@ class JsonPathSpec extends FlatSpec with Matchers with JsonPathMatchers { } it should "work with nested boolean filters" in { - val json = parseJson("""{ "conditions": + val json = parseJson( + """{ "conditions": [ { "id": "i1", "condition": true }, { "id": "i2", "condition": false } ] - }""") + }""" + ) JsonPath.query("$.conditions[?(@['condition'] == true)].id", json) should findElements(text("i1")) JsonPath.query("$.conditions[?(@['condition'] == false)].id", json) should findElements(text("i2")) @@ -884,8 +879,7 @@ class JsonPathSpec extends FlatSpec with Matchers with JsonPathMatchers { it should "work with nested objects" in { val json = parseJson(""" { "foo" : {"bar" : "baz"} }""") val x = JsonPath.query("$.foo", json) - val expected = mapper.getNodeFactory.objectNode - expected.put("bar", "baz") + val expected = JsObject(Map("bar" -> JsString("baz"))) x should findElements(expected) JsonPath.query("$.foo.bar", json) should findElements(text("baz")) JsonPath.query("$..bar", json) should findElements(text("baz")) @@ -920,7 +914,7 @@ class JsonPathSpec extends FlatSpec with Matchers with JsonPathMatchers { JsonPath.query("$..options[*]['bold','size']", json) should findOrderedElements(bool(true), int(3)) } - val ten = parseJson("[1,2,3,4,5,6,7,8,9,10]") + val ten: JsValue = parseJson("[1,2,3,4,5,6,7,8,9,10]") "Array field slicing" should "work with random accessors" in { JsonPath.query("$[0]", goessnerJson) should findElements() @@ -957,10 +951,8 @@ class JsonPathSpec extends FlatSpec with Matchers with JsonPathMatchers { "Filters" should "be applied on array children and pick all matching ones" in { val json = parseJson("""[{"foo":1},{"foo":2},{"bar":3}]""") - val expected1 = mapper.getNodeFactory.objectNode - expected1.put("foo", 1) - val expected2 = mapper.getNodeFactory.objectNode - expected2.put("foo", 2) + val expected1 = JsObject(Map("foo" -> JsNumber(BigDecimal(1)))) + val expected2 = JsObject(Map("foo" -> JsNumber(BigDecimal(2)))) JsonPath.query("$[?(@.foo)]", json) should findOrderedElements(expected1, expected2) } @@ -990,17 +982,18 @@ class JsonPathSpec extends FlatSpec with Matchers with JsonPathMatchers { val json = parseJson("""[{"foo":"a"},{"foo":"b"},{"bar":"c"}]""") - val expected = mapper.getNodeFactory.objectNode - expected.put("foo", "a") + val expected = JsObject(Map("foo" -> JsString("a"))) JsonPath.query("$[?(@.foo=='a' )]", json) should findOrderedElements(expected) } it should "work with non-alphanumeric values" in { - val json = parseJson("""{ "a":[{ "a":5, "@":2, "$":5 }, + val json = parseJson( + """{ "a":[{ "a":5, "@":2, "$":5 }, { "a":6, "@":3, "$":4 }, { "a":7, "@":4, "$":5 } - ]}""") + ]}""" + ) JsonPath.query("""$.a[?(@['@']==3)]""", json) should findElements(parseJson("""{"a":6,"@":3,"$":4}""")) JsonPath.query("""$.a[?(@['$']!=5)]""", json) should findElements(parseJson("""{"a":6,"@":3,"$":4}""")) } @@ -1030,7 +1023,7 @@ class JsonPathSpec extends FlatSpec with Matchers with JsonPathMatchers { it should "not mess up with node with the same name at different depths in the hierarchy" in { val json = """{"foo":{"nico":{"nico":42}}}""" - JsonPath.query("""$..foo[?(@.nico)]""", parseJson(json)) should findElements(parseJson("""{"nico":{"nico":42}}}""")) + JsonPath.query("""$..foo[?(@.nico)]""", parseJson(json)) should findElements(parseJson("""{"nico":{"nico":42}}""")) } "`null` elements" should "be correctly handled" in { @@ -1039,7 +1032,7 @@ class JsonPathSpec extends FlatSpec with Matchers with JsonPathMatchers { // JsonPath.query("$.foo.bar", fooNull) should findElements() val arrayWithNull = parseJson("""{"foo":[1,null,3,"woot"]}""") - JsonPath.query("$.foo[?(@==null)]", arrayWithNull) should findElements(NullNode.instance) + JsonPath.query("$.foo[?(@==null)]", arrayWithNull) should findElements(nullNode) // JsonPath.query("$.foo[?(@>=null)]", arrayWithNull) should findElements() // JsonPath.query("$.foo[?(@>=0.5)]", arrayWithNull) should findOrderedElements(int(1), int(3)) } @@ -1171,8 +1164,8 @@ class JsonPathSpec extends FlatSpec with Matchers with JsonPathMatchers { trait JsonPathMatchers { - class OrderedElementsMatcher(expected: Traversable[Any]) extends Matcher[Either[JPError, Iterator[Any]]] { - override def apply(input: Either[JPError, Iterator[Any]]): MatchResult = + class OrderedElementsMatcher(expected: Traversable[JsValue]) extends Matcher[Either[JPError, Iterator[JsValue]]] { + override def apply(input: Either[JPError, Iterator[JsValue]]): MatchResult = input match { case Right(it) => val seq = it.toVector @@ -1188,10 +1181,11 @@ trait JsonPathMatchers { ) } } - def findOrderedElements(expected: Any*) = new OrderedElementsMatcher(expected) - class ElementsMatcher(expected: Traversable[Any]) extends Matcher[Either[JPError, Iterator[Any]]] { - override def apply(input: Either[JPError, Iterator[Any]]): MatchResult = + def findOrderedElements(expected: JsValue*) = new OrderedElementsMatcher(expected) + + class ElementsMatcher(expected: Traversable[JsValue]) extends Matcher[Either[JPError, Iterator[JsValue]]] { + override def apply(input: Either[JPError, Iterator[JsValue]]): MatchResult = input match { case Right(it) => val actualSeq = it.toVector @@ -1204,7 +1198,7 @@ trait JsonPathMatchers { if (missing.isEmpty) { s"$actualSeq should not contains $added" } else if (added.isEmpty) { - s"$actualSeq is missing $missing", + s"$actualSeq is missing $missing" } else { s"$actualSeq is missing $missing and should not contains $added" }, @@ -1217,5 +1211,6 @@ trait JsonPathMatchers { ) } } - def findElements(expected: Any*) = new ElementsMatcher(expected) + + def findElements(expected: JsValue*) = new ElementsMatcher(expected) } From e84a1c6596aa98a4772ffbded1b837c0b9390ffa Mon Sep 17 00:00:00 2001 From: Aleksandr Chermenin Date: Wed, 26 Jun 2019 17:06:31 +0500 Subject: [PATCH 2/4] Implemented universal library for Jackson and Play JSON libraries --- build.sbt | 13 + core/build.sbt | 1 + dependencies.sbt => core/dependencies.sbt | 1 - .../main/scala/io/gatling/jsonpath/AST.scala | 17 +- .../io/gatling/jsonpath/BaseJsonPath.scala | 89 +++-- .../jsonpath/ComparisonOperators.scala | 25 +- .../io/gatling/jsonpath/JsonElement.scala | 42 +++ .../scala/io/gatling/jsonpath/Parser.scala | 16 +- .../jsonpath/RecursiveDataIterator.scala | 18 +- .../jsonpath/RecursiveFieldIterator.scala | 22 +- .../gatling/jsonpath/RecursiveIterator.scala | 10 +- .../jsonpath/RecursiveNodeIterator.scala | 20 +- .../gatling/jsonpath/BaseJsonPathSpec.scala | 328 +++++++++--------- .../jsonpath/ComparisonOperatorsSpec.scala | 85 +++-- .../io/gatling/jsonpath/ParserSpec.scala | 79 +++-- jackson/build.sbt | 1 + jackson/dependencies.sbt | 3 + .../gatling/jsonpath/jackson/JsonPath.scala | 63 ++++ .../jsonpath/jackson/JsonPathSpec.scala | 29 ++ play/build.sbt | 1 + play/dependencies.sbt | 3 + .../io/gatling/jsonpath/play/JsonPath.scala | 54 +++ .../gatling/jsonpath/play/JsonPathSpec.scala | 25 ++ 23 files changed, 586 insertions(+), 359 deletions(-) create mode 100644 core/build.sbt rename dependencies.sbt => core/dependencies.sbt (77%) rename {src => core/src}/main/scala/io/gatling/jsonpath/AST.scala (80%) rename src/main/scala/io/gatling/jsonpath/JsonPath.scala => core/src/main/scala/io/gatling/jsonpath/BaseJsonPath.scala (56%) rename {src => core/src}/main/scala/io/gatling/jsonpath/ComparisonOperators.scala (70%) create mode 100644 core/src/main/scala/io/gatling/jsonpath/JsonElement.scala rename {src => core/src}/main/scala/io/gatling/jsonpath/Parser.scala (95%) rename {src => core/src}/main/scala/io/gatling/jsonpath/RecursiveDataIterator.scala (71%) rename {src => core/src}/main/scala/io/gatling/jsonpath/RecursiveFieldIterator.scala (72%) rename {src => core/src}/main/scala/io/gatling/jsonpath/RecursiveIterator.scala (86%) rename {src => core/src}/main/scala/io/gatling/jsonpath/RecursiveNodeIterator.scala (67%) rename src/test/scala/io/gatling/jsonpath/JsonPathSpec.scala => core/src/test/scala/io/gatling/jsonpath/BaseJsonPathSpec.scala (78%) rename {src => core/src}/test/scala/io/gatling/jsonpath/ComparisonOperatorsSpec.scala (81%) rename {src => core/src}/test/scala/io/gatling/jsonpath/ParserSpec.scala (88%) create mode 100644 jackson/build.sbt create mode 100644 jackson/dependencies.sbt create mode 100644 jackson/src/main/scala/io/gatling/jsonpath/jackson/JsonPath.scala create mode 100644 jackson/src/test/scala/io/gatling/jsonpath/jackson/JsonPathSpec.scala create mode 100644 play/build.sbt create mode 100644 play/dependencies.sbt create mode 100644 play/src/main/scala/io/gatling/jsonpath/play/JsonPath.scala create mode 100644 play/src/test/scala/io/gatling/jsonpath/play/JsonPathSpec.scala diff --git a/build.sbt b/build.sbt index f205e19..1514cf3 100644 --- a/build.sbt +++ b/build.sbt @@ -3,6 +3,19 @@ import _root_.io.gatling.build.license._ enablePlugins(AutomateHeaderPlugin, SonatypeReleasePlugin) +name := "jsonpath" + +lazy val global = (project in file(".")) + .aggregate(core, jackson, play) + +lazy val core = project + +lazy val jackson = project + .dependsOn(core % "test->test; compile->compile") + +lazy val play = project + .dependsOn(core % "test->test; compile->compile") + projectDevelopers := Seq( GatlingDeveloper("slandelle@gatling.io", "Stéphane Landelle", isGatlingCorp = true), GatlingDeveloper("nremond@gmail.com", "Nicolas Rémond", isGatlingCorp = false) diff --git a/core/build.sbt b/core/build.sbt new file mode 100644 index 0000000..2b7d32b --- /dev/null +++ b/core/build.sbt @@ -0,0 +1 @@ +name := "jsonpath-core" diff --git a/dependencies.sbt b/core/dependencies.sbt similarity index 77% rename from dependencies.sbt rename to core/dependencies.sbt index ea36da6..edafe89 100644 --- a/dependencies.sbt +++ b/core/dependencies.sbt @@ -1,4 +1,3 @@ libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2" libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test" libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.14.0" % "test" -libraryDependencies += "com.typesafe.play" %% "play-json" % "2.7.4" \ No newline at end of file diff --git a/src/main/scala/io/gatling/jsonpath/AST.scala b/core/src/main/scala/io/gatling/jsonpath/AST.scala similarity index 80% rename from src/main/scala/io/gatling/jsonpath/AST.scala rename to core/src/main/scala/io/gatling/jsonpath/AST.scala index b2a97e4..5654bfc 100644 --- a/src/main/scala/io/gatling/jsonpath/AST.scala +++ b/core/src/main/scala/io/gatling/jsonpath/AST.scala @@ -16,8 +16,6 @@ package io.gatling.jsonpath -import play.api.libs.json.{ JsBoolean, JsNull, JsNumber, JsString, JsValue } - object AST { sealed trait AstToken sealed trait PathToken extends AstToken @@ -53,20 +51,7 @@ object AST { sealed trait FilterValue extends AstToken - object FilterDirectValue { - def long(value: Long) = FilterDirectValue(JsNumber(BigDecimal(value))) - - def double(value: Double) = FilterDirectValue(JsNumber(BigDecimal(value))) - - val True = FilterDirectValue(JsBoolean(true)) - val False = FilterDirectValue(JsBoolean(false)) - - def string(value: String) = FilterDirectValue(JsString(value)) - - val Null = FilterDirectValue(JsNull) - } - - case class FilterDirectValue(node: JsValue) extends FilterValue + case class FilterDirectValue(node: JsonElement[_]) extends FilterValue case class SubQuery(path: List[PathToken]) extends FilterValue diff --git a/src/main/scala/io/gatling/jsonpath/JsonPath.scala b/core/src/main/scala/io/gatling/jsonpath/BaseJsonPath.scala similarity index 56% rename from src/main/scala/io/gatling/jsonpath/JsonPath.scala rename to core/src/main/scala/io/gatling/jsonpath/BaseJsonPath.scala index 261574c..ac30a0c 100644 --- a/src/main/scala/io/gatling/jsonpath/JsonPath.scala +++ b/core/src/main/scala/io/gatling/jsonpath/BaseJsonPath.scala @@ -16,13 +16,13 @@ package io.gatling.jsonpath -import scala.math.abs import io.gatling.jsonpath.AST._ -import play.api.libs.json.{ JsArray, JsObject, JsValue } + +import scala.math.abs case class JPError(reason: String) -object JsonPath { +abstract class BaseJsonPath[T] { private val JsonPathParser = ThreadLocal.withInitial[Parser](() => new Parser) def compile(query: String): Either[JPError, JsonPathQuery] = @@ -31,64 +31,83 @@ object JsonPath { case ns: Parser.NoSuccess => Left(JPError(ns.msg)) } - def query(query: String, jsonObject: JsValue): Either[JPError, Iterator[JsValue]] = - compile(query).right.map(_.query(jsonObject)) + protected def mapJsonObject(jsonObject: T): JsonElement[_] + + def query(query: String, jsonObject: T): Either[JPError, Iterator[Any]] = + compile(query).right.map(_.query(mapJsonObject(jsonObject))) } class JsonPathQuery(path: List[PathToken]) extends Serializable { - def query(jsonNode: JsValue): Iterator[JsValue] = new JsonPathWalker(jsonNode, path).walk() + private def getValue(element: JsonElement[_]): Any = element match { + case obj: ObjectElement => getObject(obj) + case array: ArrayElement => getArray(array) + case LongValue(value) => value + case DoubleValue(value) => value + case BooleanValue(value) => value + case StringValue(value) => value + case NullValue => null + } + + private def getObject(obj: ObjectElement): Map[String, Any] = + obj.values.map { case (key, value) => key -> getValue(value) }.toMap + + private def getArray(array: ArrayElement): Seq[Any] = + array.values.map(getValue).toList + + def query(jsonNode: JsonElement[_]): Iterator[Any] = + new JsonPathWalker(jsonNode, path).walk().map(getValue) } -class JsonPathWalker(rootNode: JsValue, fullPath: List[PathToken]) { +class JsonPathWalker(rootNode: JsonElement[_], fullPath: List[PathToken]) { - def walk(): Iterator[JsValue] = walk(rootNode, fullPath) + def walk(): Iterator[JsonElement[_]] = walk(rootNode, fullPath) - private[this] def walk(node: JsValue, path: List[PathToken]): Iterator[JsValue] = + private[this] def walk(node: JsonElement[_], path: List[PathToken]): Iterator[JsonElement[_]] = path match { case head :: tail => walk1(node, head).flatMap(walk(_, tail)) case _ => Iterator.single(node) } - private[this] def walk1(node: JsValue, query: PathToken): Iterator[JsValue] = + private[this] def walk1(node: JsonElement[_], query: PathToken): Iterator[JsonElement[_]] = query match { case RootNode => Iterator.single(rootNode) case CurrentNode => Iterator.single(node) case Field(name) => node match { - case obj: JsObject if obj.keys.contains(name) => - Iterator.single(obj.value(name)) + case obj: ObjectElement if obj.contains(name) => + Iterator.single(obj(name)) case _ => Iterator.empty } case RecursiveField(name) => new RecursiveFieldIterator(node, name) case MultiField(fieldNames) => node match { - case obj: JsObject => + case obj: ObjectElement => // don't use collect on iterator with filter causes (executed twice) - fieldNames.iterator.filter(obj.keys.contains).map(obj.value) + fieldNames.iterator.filter(obj.contains).map(obj(_)) case _ => Iterator.empty } case AnyField => node match { - case obj: JsObject => obj.values.iterator - case _ => Iterator.empty + case obj: ObjectElement => obj.values.map(_._2) + case _ => Iterator.empty } case ArraySlice(None, None, 1) => node match { - case array: JsArray => array.value.iterator - case _ => Iterator.empty + case array: ArrayElement => array.values + case _ => Iterator.empty } case ArraySlice(start, stop, step) => node match { - case array: JsArray => sliceArray(array, start, stop, step) - case _ => Iterator.empty + case array: ArrayElement => sliceArray(array, start, stop, step) + case _ => Iterator.empty } case ArrayRandomAccess(indices) => node match { - case array: JsArray => indices.iterator.collect { - case i if i >= 0 && i < array.value.size => array(i) - case i if i < 0 && i >= -array.value.size => array(i + array.value.size) + case array: ArrayElement => indices.iterator.collect { + case i if i >= 0 && i < array.size => array(i) + case i if i < 0 && i >= -array.size => array(i + array.size) } case _ => Iterator.empty } @@ -100,33 +119,33 @@ class JsonPathWalker(rootNode: JsValue, fullPath: List[PathToken]) { case RecursiveAnyField => new RecursiveNodeIterator(node) } - private[this] def applyFilter(currentNode: JsValue, filterToken: FilterToken): Iterator[JsValue] = { + private[this] def applyFilter(currentNode: JsonElement[_], filterToken: FilterToken): Iterator[JsonElement[_]] = { - def resolveSubQuery(node: JsValue, q: List[AST.PathToken], nextOp: JsValue => Boolean): Boolean = { + def resolveSubQuery(node: JsonElement[_], q: List[AST.PathToken], nextOp: JsonElement[_] => Boolean): Boolean = { val it = walk(node, q) it.hasNext && nextOp(it.next()) } - def applyBinaryOpWithResolvedLeft(node: JsValue, op: ComparisonOperator, lhsNode: JsValue, rhs: FilterValue): Boolean = + def applyBinaryOpWithResolvedLeft(node: JsonElement[_], op: ComparisonOperator, lhsNode: JsonElement[_], rhs: FilterValue): Boolean = rhs match { case FilterDirectValue(valueNode) => op(lhsNode, valueNode) case SubQuery(q) => resolveSubQuery(node, q, op(lhsNode, _)) } - def applyBinaryOp(op: ComparisonOperator, lhs: FilterValue, rhs: FilterValue): JsValue => Boolean = + def applyBinaryOp(op: ComparisonOperator, lhs: FilterValue, rhs: FilterValue): JsonElement[_] => Boolean = lhs match { case FilterDirectValue(valueNode) => applyBinaryOpWithResolvedLeft(_, op, valueNode, rhs) case SubQuery(q) => node => resolveSubQuery(node, q, applyBinaryOpWithResolvedLeft(node, op, _, rhs)) } - def elementsToFilter(node: JsValue): Iterator[JsValue] = + def elementsToFilter(node: JsonElement[_]): Iterator[JsonElement[_]] = node match { - case array: JsArray => array.value.iterator - case obj: JsObject => Iterator.single(obj) - case _ => Iterator.empty + case array: ArrayElement => array.values + case obj: ObjectElement => Iterator.single(obj) + case _ => Iterator.empty } - def evaluateFilter(filterToken: FilterToken): JsValue => Boolean = + def evaluateFilter(filterToken: FilterToken): JsonElement[_] => Boolean = filterToken match { case HasFilter(subQuery) => walk(_, subQuery.path).hasNext @@ -144,8 +163,8 @@ class JsonPathWalker(rootNode: JsValue, fullPath: List[PathToken]) { elementsToFilter(currentNode).filter(filterFunction) } - private[this] def sliceArray(array: JsArray, start: Option[Int], stop: Option[Int], step: Int): Iterator[JsValue] = { - val size = array.value.size + private[this] def sliceArray(array: ArrayElement, start: Option[Int], stop: Option[Int], step: Int): Iterator[JsonElement[_]] = { + val size = array.size def lenRelative(x: Int) = if (x >= 0) x else size + x @@ -163,7 +182,7 @@ class JsonPathWalker(rootNode: JsValue, fullPath: List[PathToken]) { } val absStep = abs(step) - val elements: Iterator[JsValue] = if (step < 0) Iterator.range(array.value.size - 1, -1, -1).map(array.value) else array.value.iterator + val elements: Iterator[JsonElement[_]] = if (step < 0) Iterator.range(array.size - 1, -1, -1).map(array(_)) else array.values val fromStartToEnd = elements.slice(absStart, absEnd) if (absStep == 1) diff --git a/src/main/scala/io/gatling/jsonpath/ComparisonOperators.scala b/core/src/main/scala/io/gatling/jsonpath/ComparisonOperators.scala similarity index 70% rename from src/main/scala/io/gatling/jsonpath/ComparisonOperators.scala rename to core/src/main/scala/io/gatling/jsonpath/ComparisonOperators.scala index 73f2473..daf103d 100644 --- a/src/main/scala/io/gatling/jsonpath/ComparisonOperators.scala +++ b/core/src/main/scala/io/gatling/jsonpath/ComparisonOperators.scala @@ -16,10 +16,8 @@ package io.gatling.jsonpath -import play.api.libs.json.{ JsBoolean, JsNull, JsNumber, JsString, JsValue } - sealed trait ComparisonOperator { - def apply(lhs: JsValue, rhs: JsValue): Boolean + def apply(lhs: JsonElement[_], rhs: JsonElement[_]): Boolean } // Comparison operators @@ -27,12 +25,15 @@ sealed trait ComparisonWithOrderingOperator extends ComparisonOperator { protected def compare[T: Ordering](lhs: T, rhs: T): Boolean - def apply(lhs: JsValue, rhs: JsValue): Boolean = + def apply(lhs: JsonElement[_], rhs: JsonElement[_]): Boolean = (lhs, rhs) match { - case (JsString(left), JsString(right)) => compare(left, right) - case (JsBoolean(left), JsBoolean(right)) => compare(left, right) - case (JsNumber(left), JsNumber(right)) => compare(left, right) - case _ => false + case (StringValue(left), StringValue(right)) => compare(left, right) + case (BooleanValue(left), BooleanValue(right)) => compare(left, right) + case (LongValue(left), LongValue(right)) => compare(left, right) + case (LongValue(left), DoubleValue(right)) => compare(left.doubleValue(), right) + case (DoubleValue(left), LongValue(right)) => compare(left, right.doubleValue()) + case (DoubleValue(left), DoubleValue(right)) => compare(left, right) + case _ => false } } @@ -41,15 +42,15 @@ case object EqWithOrderingOperator extends ComparisonWithOrderingOperator { } case object EqOperator extends ComparisonOperator { - override def apply(lhs: JsValue, rhs: JsValue): Boolean = + override def apply(lhs: JsonElement[_], rhs: JsonElement[_]): Boolean = (lhs, rhs) match { - case (JsNull, JsNull) => true - case _ => EqWithOrderingOperator(lhs, rhs) + case (NullValue, NullValue) => true + case _ => EqWithOrderingOperator(lhs, rhs) } } case object NotEqOperator extends ComparisonOperator { - override def apply(lhs: JsValue, rhs: JsValue): Boolean = !EqOperator(lhs, rhs) + override def apply(lhs: JsonElement[_], rhs: JsonElement[_]): Boolean = !EqOperator(lhs, rhs) } case object LessOperator extends ComparisonWithOrderingOperator { diff --git a/core/src/main/scala/io/gatling/jsonpath/JsonElement.scala b/core/src/main/scala/io/gatling/jsonpath/JsonElement.scala new file mode 100644 index 0000000..0140ac5 --- /dev/null +++ b/core/src/main/scala/io/gatling/jsonpath/JsonElement.scala @@ -0,0 +1,42 @@ +package io.gatling.jsonpath + +sealed trait JsonElement[T] + +abstract class ObjectElement extends JsonElement[ObjectElement] { + def contains(key: String): Boolean + def apply(key: String): JsonElement[_] + def values: Iterator[(String, JsonElement[_])] + def size: Int + def isEmpty: Boolean + def nonEmpty: Boolean = !isEmpty +} + +abstract class ArrayElement extends JsonElement[ArrayElement] { + def values: Iterator[JsonElement[_]] + def apply(index: Int): JsonElement[_] + def size: Int + def isEmpty: Boolean + def nonEmpty: Boolean = !isEmpty +} + +abstract class ValueElement[T <: ValueElement[T]] extends JsonElement[T] with Comparable[T] + +case class LongValue(value: Long) extends ValueElement[LongValue] { + override def compareTo(o: LongValue): Int = value.compareTo(o.value) +} + +case class DoubleValue(value: Double) extends ValueElement[DoubleValue] { + override def compareTo(o: DoubleValue): Int = value.compareTo(o.value) +} + +case class BooleanValue(value: Boolean) extends ValueElement[BooleanValue] { + override def compareTo(o: BooleanValue): Int = value.compareTo(o.value) +} + +case class StringValue(value: String) extends ValueElement[StringValue] { + override def compareTo(o: StringValue): Int = value.compareTo(o.value) +} + +object NullValue extends ValueElement[Nothing] { + override def compareTo(o: Nothing): Int = 0 +} diff --git a/src/main/scala/io/gatling/jsonpath/Parser.scala b/core/src/main/scala/io/gatling/jsonpath/Parser.scala similarity index 95% rename from src/main/scala/io/gatling/jsonpath/Parser.scala rename to core/src/main/scala/io/gatling/jsonpath/Parser.scala index 67c40fe..95c5762 100644 --- a/src/main/scala/io/gatling/jsonpath/Parser.scala +++ b/core/src/main/scala/io/gatling/jsonpath/Parser.scala @@ -18,10 +18,10 @@ package io.gatling.jsonpath import java.lang.{ StringBuilder => JStringBuilder } -import scala.util.parsing.combinator.RegexParsers - import io.gatling.jsonpath.AST._ +import scala.util.parsing.combinator.RegexParsers + class StringBuilderPool extends ThreadLocal[JStringBuilder] { override def initialValue() = new JStringBuilder(512) @@ -115,18 +115,20 @@ object Parser extends RegexParsers { /// filters parsers /////////////////////////////////////////////////////// private def numberValue: Parser[FilterDirectValue] = NumberValueRegex ^^ { - s => if (s.indexOf('.') != -1) FilterDirectValue.double(s.toDouble) else FilterDirectValue.long(s.toLong) + s => + if (s.indexOf('.') != -1) FilterDirectValue(DoubleValue(s.toDouble)) + else FilterDirectValue(LongValue(s.toLong)) } private def booleanValue: Parser[FilterDirectValue] = - "true" ^^^ FilterDirectValue.True | - "false" ^^^ FilterDirectValue.False + "true" ^^^ FilterDirectValue(BooleanValue(true)) | + "false" ^^^ FilterDirectValue(BooleanValue(false)) private def nullValue: Parser[FilterValue] = - "null" ^^^ FilterDirectValue.Null + "null" ^^^ FilterDirectValue(NullValue) private def stringValue: Parser[FilterDirectValue] = quotedValue ^^ { - FilterDirectValue.string + str => FilterDirectValue(StringValue(str)) } private def value: Parser[FilterValue] = booleanValue | numberValue | nullValue | stringValue diff --git a/src/main/scala/io/gatling/jsonpath/RecursiveDataIterator.scala b/core/src/main/scala/io/gatling/jsonpath/RecursiveDataIterator.scala similarity index 71% rename from src/main/scala/io/gatling/jsonpath/RecursiveDataIterator.scala rename to core/src/main/scala/io/gatling/jsonpath/RecursiveDataIterator.scala index 85498a8..80d4e13 100644 --- a/src/main/scala/io/gatling/jsonpath/RecursiveDataIterator.scala +++ b/core/src/main/scala/io/gatling/jsonpath/RecursiveDataIterator.scala @@ -16,16 +16,14 @@ package io.gatling.jsonpath -import play.api.libs.json.{ JsArray, JsObject, JsValue } - /** * Collect all nodes data (objects and leaves) * * @param root the tree root */ -class RecursiveDataIterator(root: JsValue) extends RecursiveIterator[Iterator[JsValue]](root) { +class RecursiveDataIterator(root: JsonElement[_]) extends RecursiveIterator[Iterator[JsonElement[_]]](root) { - override protected def visit(it: Iterator[JsValue]): Unit = { + override protected def visit(it: Iterator[JsonElement[_]]): Unit = { while (it.hasNext && !pause) { visitNode(it.next()) } @@ -34,18 +32,18 @@ class RecursiveDataIterator(root: JsValue) extends RecursiveIterator[Iterator[Js } } - override protected def visitNode(node: JsValue): Unit = + override protected def visitNode(node: JsonElement[_]): Unit = node match { - case obj: JsObject => - if (obj.value.nonEmpty) { + case obj: ObjectElement => + if (obj.nonEmpty) { // only non empty objects - val it = obj.value.values.iterator + val it = obj.values.map(_._2) stack = it :: stack nextNode = node pause = true } - case array: JsArray => - val it = array.value.iterator + case array: ArrayElement => + val it = array.values stack = it :: stack visit(it) case _ => diff --git a/src/main/scala/io/gatling/jsonpath/RecursiveFieldIterator.scala b/core/src/main/scala/io/gatling/jsonpath/RecursiveFieldIterator.scala similarity index 72% rename from src/main/scala/io/gatling/jsonpath/RecursiveFieldIterator.scala rename to core/src/main/scala/io/gatling/jsonpath/RecursiveFieldIterator.scala index efe273f..252da34 100644 --- a/src/main/scala/io/gatling/jsonpath/RecursiveFieldIterator.scala +++ b/core/src/main/scala/io/gatling/jsonpath/RecursiveFieldIterator.scala @@ -16,17 +16,15 @@ package io.gatling.jsonpath -import play.api.libs.json.{ JsArray, JsObject, JsValue } - sealed trait VisitedIterator { def hasNext: Boolean } -case class VisitedObject(it: Iterator[(String, JsValue)]) extends VisitedIterator { +case class VisitedObject(it: Iterator[(String, JsonElement[_])]) extends VisitedIterator { override def hasNext: Boolean = it.hasNext } -case class VisitedArray(it: Iterator[JsValue]) extends VisitedIterator { +case class VisitedArray(it: Iterator[JsonElement[_]]) extends VisitedIterator { override def hasNext: Boolean = it.hasNext } @@ -36,14 +34,14 @@ case class VisitedArray(it: Iterator[JsValue]) extends VisitedIterator { * @param root the tree root * @param name the searched name */ -class RecursiveFieldIterator(root: JsValue, name: String) extends RecursiveIterator[VisitedIterator](root) { +class RecursiveFieldIterator(root: JsonElement[_], name: String) extends RecursiveIterator[VisitedIterator](root) { override def visit(t: VisitedIterator): Unit = t match { case VisitedObject(it) => visitObject(it) case VisitedArray(it) => visitArray(it) } - private def visitObject(it: Iterator[(String, JsValue)]): Unit = { + private def visitObject(it: Iterator[(String, JsonElement[_])]): Unit = { while (it.hasNext && !pause) { it.next() match { case (key, value) if key == name => @@ -57,7 +55,7 @@ class RecursiveFieldIterator(root: JsValue, name: String) extends RecursiveItera } } - private def visitArray(it: Iterator[JsValue]): Unit = { + private def visitArray(it: Iterator[JsonElement[_]]): Unit = { while (it.hasNext && !pause) { visitNode(it.next()) } @@ -66,14 +64,14 @@ class RecursiveFieldIterator(root: JsValue, name: String) extends RecursiveItera } } - protected def visitNode(node: JsValue): Unit = + protected def visitNode(node: JsonElement[_]): Unit = node match { - case obj: JsObject => - val it = obj.value.iterator + case obj: ObjectElement => + val it = obj.values stack = VisitedObject(it) :: stack visitObject(it) - case array: JsArray => - val it = array.value.iterator + case array: ArrayElement => + val it = array.values stack = VisitedArray(it) :: stack visitArray(it) case _ => diff --git a/src/main/scala/io/gatling/jsonpath/RecursiveIterator.scala b/core/src/main/scala/io/gatling/jsonpath/RecursiveIterator.scala similarity index 86% rename from src/main/scala/io/gatling/jsonpath/RecursiveIterator.scala rename to core/src/main/scala/io/gatling/jsonpath/RecursiveIterator.scala index 50e8fca..5a6a683 100644 --- a/src/main/scala/io/gatling/jsonpath/RecursiveIterator.scala +++ b/core/src/main/scala/io/gatling/jsonpath/RecursiveIterator.scala @@ -16,18 +16,16 @@ package io.gatling.jsonpath -import play.api.libs.json.JsValue - import scala.collection.AbstractIterator -abstract class RecursiveIterator[T](root: JsValue) extends AbstractIterator[JsValue] { +abstract class RecursiveIterator[T](root: JsonElement[_]) extends AbstractIterator[JsonElement[_]] { - protected var nextNode: JsValue = _ + protected var nextNode: JsonElement[_] = _ protected var finished: Boolean = _ protected var pause: Boolean = _ protected var stack: List[T] = _ - protected def visitNode(node: JsValue): Unit + protected def visitNode(node: JsonElement[_]): Unit protected def visit(t: T): Unit @@ -49,7 +47,7 @@ abstract class RecursiveIterator[T](root: JsValue) extends AbstractIterator[JsVa !finished } - override def next(): JsValue = + override def next(): JsonElement[_] = if (finished) { throw new UnsupportedOperationException("Can't call next on empty Iterator") } else if (nextNode == null) { diff --git a/src/main/scala/io/gatling/jsonpath/RecursiveNodeIterator.scala b/core/src/main/scala/io/gatling/jsonpath/RecursiveNodeIterator.scala similarity index 67% rename from src/main/scala/io/gatling/jsonpath/RecursiveNodeIterator.scala rename to core/src/main/scala/io/gatling/jsonpath/RecursiveNodeIterator.scala index bba2354..1dbeac7 100644 --- a/src/main/scala/io/gatling/jsonpath/RecursiveNodeIterator.scala +++ b/core/src/main/scala/io/gatling/jsonpath/RecursiveNodeIterator.scala @@ -16,16 +16,14 @@ package io.gatling.jsonpath -import play.api.libs.json.{ JsArray, JsObject, JsValue } - /** * Collect all nodes * * @param root the tree root */ -class RecursiveNodeIterator(root: JsValue) extends RecursiveIterator[Iterator[JsValue]](root) { +class RecursiveNodeIterator(root: JsonElement[_]) extends RecursiveIterator[Iterator[JsonElement[_]]](root) { - override protected def visit(it: Iterator[JsValue]): Unit = { + override protected def visit(it: Iterator[JsonElement[_]]): Unit = { while (it.hasNext && !pause) { visitNode(it.next()) } @@ -34,17 +32,17 @@ class RecursiveNodeIterator(root: JsValue) extends RecursiveIterator[Iterator[Js } } - override protected def visitNode(node: JsValue): Unit = + override protected def visitNode(node: JsonElement[_]): Unit = node match { - case obj: JsObject => - if (obj.value.nonEmpty) { - stack = obj.value.values.iterator :: stack + case obj: ObjectElement => + if (obj.nonEmpty) { + stack = obj.values.map(_._2) :: stack } nextNode = node pause = true - case array: JsArray => - if (array.value.nonEmpty) { - stack = array.value.iterator :: stack + case array: ArrayElement => + if (array.nonEmpty) { + stack = array.values :: stack } nextNode = node pause = true diff --git a/src/test/scala/io/gatling/jsonpath/JsonPathSpec.scala b/core/src/test/scala/io/gatling/jsonpath/BaseJsonPathSpec.scala similarity index 78% rename from src/test/scala/io/gatling/jsonpath/JsonPathSpec.scala rename to core/src/test/scala/io/gatling/jsonpath/BaseJsonPathSpec.scala index b06bf91..409d0ba 100644 --- a/src/test/scala/io/gatling/jsonpath/JsonPathSpec.scala +++ b/core/src/test/scala/io/gatling/jsonpath/BaseJsonPathSpec.scala @@ -18,28 +18,31 @@ package io.gatling.jsonpath import org.scalatest.{ FlatSpec, Matchers } import org.scalatest.matchers.{ MatchResult, Matcher } -import play.api.libs.json.{ JsBoolean, JsNull, JsNumber, JsObject, JsString, JsValue, Json } -class JsonPathSpec extends FlatSpec with Matchers with JsonPathMatchers { - - def parseJson(s: String): JsValue = Json.parse(s) - def bool(b: Boolean): JsValue = JsBoolean(b) - def int(i: Int): JsValue = JsNumber(BigDecimal(i)) - def double(f: Double): JsValue = JsNumber(BigDecimal(f)) - def text(s: String): JsValue = JsString(s) - def nullNode: JsValue = JsNull - - // Goessner JSON example - - val book1: String = """{"category":"reference","author":"Nigel Rees","title":"Sayings of the Century","price":8.95}""" - val book2: String = """{"category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99}""" - val book3: String = """{"category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99}""" - val book4: String = """{"category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":22.99}""" - val allBooks: String = s"[$book1,$book2,$book3,$book4]" - val bicycle: String = s"""{"color":"red","price":19.95}""" - val allStore: String = s"""{"book":$allBooks, "bicycle":$bicycle}""" - val goessnerData: String = s"""{"store":$allStore}""" - val goessnerJson: JsValue = parseJson(goessnerData) +abstract class BaseJsonPathSpec[T] extends FlatSpec with Matchers with JsonPathMatchers { + + def parseJson(s: String): T + def JsonPath: BaseJsonPath[T] + + // Goessner JSON exemple + + val book1Json: String = """{"category":"reference","author":"Nigel Rees","title":"Sayings of the Century","price":8.95}""" + val book1Map: Map[String, Any] = Map("category" -> "reference", "author" -> "Nigel Rees", "title" -> "Sayings of the Century", "price" -> 8.95) + val book2Json: String = """{"category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99}""" + val book2Map: Map[String, Any] = Map("category" -> "fiction", "author" -> "Evelyn Waugh", "title" -> "Sword of Honour", "price" -> 12.99) + val book3Json: String = """{"category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99}""" + val book3Map: Map[String, Any] = Map("author" -> "Herman Melville", "price" -> 8.99, "isbn" -> "0-553-21311-3", "category" -> "fiction", "title" -> "Moby Dick") + val book4Json: String = """{"category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":22.99}""" + val book4Map: Map[String, Any] = Map("author" -> "J. R. R. Tolkien", "price" -> 22.99, "isbn" -> "0-395-19395-8", "category" -> "fiction", "title" -> "The Lord of the Rings") + val allBooksJson: String = s"[$book1Json,$book2Json,$book3Json,$book4Json]" + val allBooksList: List[Map[String, Any]] = List(book1Map, book2Map, book3Map, book4Map) + val bicycleJson: String = s"""{"color":"red","price":19.95}""" + val bicycleMap: Map[String, Any] = Map("color" -> "red", "price" -> 19.95) + val allStoreJson: String = s"""{"book":$allBooksJson, "bicycle":$bicycleJson}""" + val allStoreMap: Map[String, Any] = Map("book" -> allBooksList, "bicycle" -> bicycleMap) + val goessnerData: String = s"""{"store":$allStoreJson}""" + val goessnerMap: Map[String, Any] = Map("store" -> allStoreMap) + val goessnerJson: T = parseJson(goessnerData) val json: String = """[ @@ -794,29 +797,29 @@ class JsonPathSpec extends FlatSpec with Matchers with JsonPathMatchers { "Keys starting with number" should "be handled properly" in { val json = parseJson(""" {"a": "b", "2": 2, "51a": "t"} """) - JsonPath.query("$.2", json) should findOrderedElements(int(2)) - JsonPath.query("$.51a", json) should findOrderedElements(text("t")) + JsonPath.query("$.2", json) should findOrderedElements(2) + JsonPath.query("$.51a", json) should findOrderedElements("t") } "Support of Goessner test cases" should "work with test set 1" in { val json = parseJson("""{"a":"a","b":"b","c d":"e"}""") - JsonPath.query("$.a", json) should findElements(text("a")) - JsonPath.query("$['a']", json) should findElements(text("a")) + JsonPath.query("$.a", json) should findElements("a") + JsonPath.query("$['a']", json) should findElements("a") // Not supported syntax "$.'c d'", here is an alternative to it - JsonPath.query("$['c d']", json) should findElements(text("e")) - JsonPath.query("$.*", json) should findElements(text("a"), text("b"), text("e")) - JsonPath.query("$['*']", json) should findElements(text("a"), text("b"), text("e")) + JsonPath.query("$['c d']", json) should findElements("e") + JsonPath.query("$.*", json) should findElements("a", "b", "e") + JsonPath.query("$['*']", json) should findElements("a", "b", "e") // Not supported syntax "$[*]" ... shouldn't that operator only apply on arrays ? } it should "work with test set 2" in { val json = parseJson("""[ 1, "2", 3.14, true, null ]""") - JsonPath.query("$[0]", json) should findOrderedElements(int(1)) - JsonPath.query("$[4]", json) should findOrderedElements(nullNode) + JsonPath.query("$[0]", json) should findOrderedElements(1) + JsonPath.query("$[4]", json) should findOrderedElements(null) JsonPath.query("$[*]", json) should findOrderedElements( - int(1), text("2"), double(3.14), bool(true), nullNode + 1, "2", 3.14, true, null ) - JsonPath.query("$[-1:]", json) should findOrderedElements(nullNode) + JsonPath.query("$[-1:]", json) should findOrderedElements(null) } it should "work with test set 3" in { @@ -832,13 +835,13 @@ class JsonPathSpec extends FlatSpec with Matchers with JsonPathMatchers { }""" ) - JsonPath.query("$.points[1]", json) should findOrderedElements(parseJson("""{ "id":"i2", "x":-2, "y": 2, "z":1 }""")) - JsonPath.query("$.points[4].x", json) should findOrderedElements(int(0)) - JsonPath.query("$.points[?(@['id']=='i4')].x", json) should findOrderedElements(int(-6)) - JsonPath.query("$.points[*].x", json) should findOrderedElements(int(4), int(-2), int(8), int(-6), int(0), int(1)) + JsonPath.query("$.points[1]", json) should findOrderedElements(Map("id" -> "i2", "x" -> -2, "y" -> 2, "z" -> 1)) + JsonPath.query("$.points[4].x", json) should findOrderedElements(0) + JsonPath.query("$.points[?(@['id']=='i4')].x", json) should findOrderedElements(-6) + JsonPath.query("$.points[*].x", json) should findOrderedElements(4, -2, 8, -6, 0, 1) // Non supported syntax "$['points'][?(@['x']*@['x']+@['y']*@['y'] > 50)].id" - JsonPath.query("$['points'][?(@['y'] >= 3)].id", json) should findOrderedElements(text("i3"), text("i6")) - JsonPath.query("$.points[?(@['z'])].id", json) should findOrderedElements(text("i2"), text("i5")) + JsonPath.query("$['points'][?(@['y'] >= 3)].id", json) should findOrderedElements("i3", "i6") + JsonPath.query("$.points[?(@['z'])].id", json) should findOrderedElements("i2", "i5") // Non supported syntax "$.points[(count(@)-1)].id" } @@ -849,9 +852,9 @@ class JsonPathSpec extends FlatSpec with Matchers with JsonPathMatchers { }""" ) - JsonPath.query("$.conditions[?(@ == true)]", json) should findElements(bool(true), bool(true)) - JsonPath.query("$.conditions[?(@ == false)]", json) should findElements(bool(false)) - JsonPath.query("$.conditions[?(false == @)]", json) should findElements(bool(false)) + JsonPath.query("$.conditions[?(@ == true)]", json) should findElements(true, true) + JsonPath.query("$.conditions[?(@ == false)]", json) should findElements(false) + JsonPath.query("$.conditions[?(false == @)]", json) should findElements(false) } it should "work with nested boolean filters" in { @@ -864,35 +867,34 @@ class JsonPathSpec extends FlatSpec with Matchers with JsonPathMatchers { }""" ) - JsonPath.query("$.conditions[?(@['condition'] == true)].id", json) should findElements(text("i1")) - JsonPath.query("$.conditions[?(@['condition'] == false)].id", json) should findElements(text("i2")) + JsonPath.query("$.conditions[?(@['condition'] == true)].id", json) should findElements("i1") + JsonPath.query("$.conditions[?(@['condition'] == false)].id", json) should findElements("i2") } "Field accessors" should "work with a simple object" in { val json = parseJson("""{"foo" : "bar"}""") - JsonPath.query("$.*", json) should findElements(text("bar")) - JsonPath.query("$.foo", json) should findElements(text("bar")) - JsonPath.query("$..foo", json) should findElements(text("bar")) + JsonPath.query("$.*", json) should findElements("bar") + JsonPath.query("$.foo", json) should findElements("bar") + JsonPath.query("$..foo", json) should findElements("bar") JsonPath.query("$.bar", json) should findElements() } it should "work with nested objects" in { val json = parseJson(""" { "foo" : {"bar" : "baz"} }""") val x = JsonPath.query("$.foo", json) - val expected = JsObject(Map("bar" -> JsString("baz"))) - x should findElements(expected) - JsonPath.query("$.foo.bar", json) should findElements(text("baz")) - JsonPath.query("$..bar", json) should findElements(text("baz")) + x should findElements(Map("bar" -> "baz")) + JsonPath.query("$.foo.bar", json) should findElements("baz") + JsonPath.query("$..bar", json) should findElements("baz") } it should "work with arrays" in { val json = parseJson("""{"foo":[{"lang":"en"},{"lang":"fr"}]}""") - JsonPath.query("$.foo[*].lang", json) should findOrderedElements(text("en"), text("fr")) + JsonPath.query("$.foo[*].lang", json) should findOrderedElements("en", "fr") } it should "work with null elements when fetching node" in { val json = parseJson("""{"foo":null}""") - JsonPath.query("$.foo", json) should findElements(nullNode) + JsonPath.query("$.foo", json) should findElements(null) } it should "work with null elements when fetching children" in { @@ -903,88 +905,81 @@ class JsonPathSpec extends FlatSpec with Matchers with JsonPathMatchers { it should "work with nested arrays" in { val json = parseJson("""[[{"foo":1}]]""") JsonPath.query("$.foo", json) should findOrderedElements() - JsonPath.query("$..foo", json) should findOrderedElements(int(1)) - JsonPath.query("$[0][0].foo", json) should findOrderedElements(int(1)) + JsonPath.query("$..foo", json) should findOrderedElements(1) + JsonPath.query("$[0][0].foo", json) should findOrderedElements(1) } "Multi-fields accessors" should "be interpreted correctly" in { val json = parseJson("""{"menu":{"year":2013,"file":"open","options":[{"bold":true},{"font":"helvetica"},{"size":3}]}}""") - JsonPath.query("$.menu['file','year']", json) should findElements(text("open"), int(2013)) + JsonPath.query("$.menu['file','year']", json) should findElements("open", 2013) JsonPath.query("$..options['foo','bar']", json) should findElements() - JsonPath.query("$..options[*]['bold','size']", json) should findOrderedElements(bool(true), int(3)) + JsonPath.query("$..options[*]['bold','size']", json) should findOrderedElements(true, 3) } - val ten: JsValue = parseJson("[1,2,3,4,5,6,7,8,9,10]") + val ten: T = parseJson("[1,2,3,4,5,6,7,8,9,10]") "Array field slicing" should "work with random accessors" in { JsonPath.query("$[0]", goessnerJson) should findElements() - JsonPath.query("$[0]", ten) should findOrderedElements(int(1)) - JsonPath.query("$[-1]", ten) should findOrderedElements(int(10)) - JsonPath.query("$[9]", ten) should findOrderedElements(int(10)) - JsonPath.query("$[2,7]", ten) should findOrderedElements(int(3), int(8)) - JsonPath.query("$[2,-7]", ten) should findOrderedElements(int(3), int(4)) - JsonPath.query("$[2,45]", ten) should findOrderedElements(int(3)) + JsonPath.query("$[0]", ten) should findOrderedElements(1) + JsonPath.query("$[-1]", ten) should findOrderedElements(10) + JsonPath.query("$[9]", ten) should findOrderedElements(10) + JsonPath.query("$[2,7]", ten) should findOrderedElements(3, 8) + JsonPath.query("$[2,-7]", ten) should findOrderedElements(3, 4) + JsonPath.query("$[2,45]", ten) should findOrderedElements(3) } it should "work when the slice operator has one separator" in { JsonPath.query("$[:-1]", goessnerJson) should findElements() JsonPath.query("$[:]", ten) should findOrderedElements( - int(1), int(2), int(3), int(4), int(5), int(6), int(7), int(8), int(9), int(10) + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ) - JsonPath.query("$[7:]", ten) should findOrderedElements(int(8), int(9), int(10)) - JsonPath.query("$[-2:]", ten) should findOrderedElements(int(9), int(10)) - JsonPath.query("$[:3]", ten) should findOrderedElements(int(1), int(2), int(3)) - JsonPath.query("$[:-7]", ten) should findOrderedElements(int(1), int(2), int(3)) - JsonPath.query("$[3:6]", ten) should findOrderedElements(int(4), int(5), int(6)) - JsonPath.query("$[-5:-2]", ten) should findOrderedElements(int(6), int(7), int(8)) + JsonPath.query("$[7:]", ten) should findOrderedElements(8, 9, 10) + JsonPath.query("$[-2:]", ten) should findOrderedElements(9, 10) + JsonPath.query("$[:3]", ten) should findOrderedElements(1, 2, 3) + JsonPath.query("$[:-7]", ten) should findOrderedElements(1, 2, 3) + JsonPath.query("$[3:6]", ten) should findOrderedElements(4, 5, 6) + JsonPath.query("$[-5:-2]", ten) should findOrderedElements(6, 7, 8) } it should "work when the slice operator has two separators" in { - JsonPath.query("$[:6:2]", ten) should findOrderedElements(int(1), int(3), int(5)) - JsonPath.query("$[1:9:3]", ten) should findOrderedElements(int(2), int(5), int(8)) - JsonPath.query("$[:5:-1]", ten) should findOrderedElements(int(10), int(9), int(8), int(7)) - JsonPath.query("$[:-4:-1]", ten) should findOrderedElements(int(10), int(9), int(8)) - JsonPath.query("$[3::-1]", ten) should findOrderedElements(int(4), int(3), int(2), int(1)) - JsonPath.query("$[-8::-1]", ten) should findOrderedElements(int(3), int(2), int(1)) + JsonPath.query("$[:6:2]", ten) should findOrderedElements(1, 3, 5) + JsonPath.query("$[1:9:3]", ten) should findOrderedElements(2, 5, 8) + JsonPath.query("$[:5:-1]", ten) should findOrderedElements(10, 9, 8, 7) + JsonPath.query("$[:-4:-1]", ten) should findOrderedElements(10, 9, 8) + JsonPath.query("$[3::-1]", ten) should findOrderedElements(4, 3, 2, 1) + JsonPath.query("$[-8::-1]", ten) should findOrderedElements(3, 2, 1) } "Filters" should "be applied on array children and pick all matching ones" in { val json = parseJson("""[{"foo":1},{"foo":2},{"bar":3}]""") - - val expected1 = JsObject(Map("foo" -> JsNumber(BigDecimal(1)))) - val expected2 = JsObject(Map("foo" -> JsNumber(BigDecimal(2)))) - - JsonPath.query("$[?(@.foo)]", json) should findOrderedElements(expected1, expected2) + JsonPath.query("$[?(@.foo)]", json) should findOrderedElements(Map("foo" -> 1), Map("foo" -> 2)) } it should "work with a deep subquery" in { val json2 = parseJson("""{"all":[{"foo":{"bar":1,"baz":2}},{"foo":3}]}""") - JsonPath.query("$.all[?(@.foo.bar)]", json2) should findElements(parseJson("""{"foo":{"bar":1,"baz":2}}""")) + JsonPath.query("$.all[?(@.foo.bar)]", json2) should findElements(Map("foo" -> Map("bar" -> 1, "baz" -> 2))) } it should "pick only proper node" in { val json3 = parseJson("""{ "foo":{"bar":1, "baz":2}, "second":{"bar":3} }""") - JsonPath.query("$..[?(@.baz)]", json3) should findElements(parseJson("""{"bar":1,"baz":2}""")) + JsonPath.query("$..[?(@.baz)]", json3) should findElements(Map("bar" -> 1, "baz" -> 2)) } it should "return only one result with object nested in object 1" in { - JsonPath.query("""$..state..[?(@.text == "(20)")].text""", parseJson(searches)) should findOrderedElements(text("(20)")) + JsonPath.query("""$..state..[?(@.text == "(20)")].text""", parseJson(searches)) should findOrderedElements("(20)") } it should "return only one result with object nested in object 2" in { - JsonPath.query("""$..[?(@.text == "(20)")].text""", parseJson(searches)) should findOrderedElements(text("(20)")) + JsonPath.query("""$..[?(@.text == "(20)")].text""", parseJson(searches)) should findOrderedElements("(20)") } it should "work with some boolean operators" in { val oneToFive = parseJson("[1,2,3,4,5]") - JsonPath.query("$[?(@ > 3)]", oneToFive) should findOrderedElements(int(4), int(5)) - JsonPath.query("$[?(@ == 3)]", oneToFive) should findOrderedElements(int(3)) + JsonPath.query("$[?(@ > 3)]", oneToFive) should findOrderedElements(4, 5) + JsonPath.query("$[?(@ == 3)]", oneToFive) should findOrderedElements(3) val json = parseJson("""[{"foo":"a"},{"foo":"b"},{"bar":"c"}]""") - - val expected = JsObject(Map("foo" -> JsString("a"))) - - JsonPath.query("$[?(@.foo=='a' )]", json) should findOrderedElements(expected) + JsonPath.query("$[?(@.foo=='a' )]", json) should findOrderedElements(Map("foo" -> "a")) } it should "work with non-alphanumeric values" in { @@ -994,52 +989,52 @@ class JsonPathSpec extends FlatSpec with Matchers with JsonPathMatchers { { "a":7, "@":4, "$":5 } ]}""" ) - JsonPath.query("""$.a[?(@['@']==3)]""", json) should findElements(parseJson("""{"a":6,"@":3,"$":4}""")) - JsonPath.query("""$.a[?(@['$']!=5)]""", json) should findElements(parseJson("""{"a":6,"@":3,"$":4}""")) + JsonPath.query("""$.a[?(@['@']==3)]""", json) should findElements(Map("a" -> 6, "@" -> 3, "$" -> 4)) + JsonPath.query("""$.a[?(@['$']!=5)]""", json) should findElements(Map("a" -> 6, "@" -> 3, "$" -> 4)) } it should "work with some predefined comparison operators" in { val oneToSeven = parseJson("[1,2,3,4,5,6,7]") JsonPath.query("$[0][?(@>1)]", oneToSeven) should findElements() - JsonPath.query("$[?( @>1 && @<=4 )]", oneToSeven) should findOrderedElements(int(2), int(3), int(4)) - JsonPath.query("$[?( @>6 && @<2 || @==3 || @<=4 && @>=4 )]", oneToSeven) should findOrderedElements(int(3), int(4)) - JsonPath.query("$[?( @==7 || @<=4 && @>1)]", oneToSeven) should findOrderedElements(int(2), int(3), int(4), int(7)) - JsonPath.query("$[?( @==1 || @>4 )]", oneToSeven) should findOrderedElements(int(1), int(5), int(6), int(7)) + JsonPath.query("$[?( @>1 && @<=4 )]", oneToSeven) should findOrderedElements(2, 3, 4) + JsonPath.query("$[?( @>6 && @<2 || @==3 || @<=4 && @>=4 )]", oneToSeven) should findOrderedElements(3, 4) + JsonPath.query("$[?( @==7 || @<=4 && @>1)]", oneToSeven) should findOrderedElements(2, 3, 4, 7) + JsonPath.query("$[?( @==1 || @>4 )]", oneToSeven) should findOrderedElements(1, 5, 6, 7) } it should "support reference to the root-node" in { val authors = """[{"pseudo":"Tolkien","name": "J. R. R. Tolkien"},{"pseudo":"Hugo","name":"Victor Hugo"}]""" - val library = parseJson(s"""{"book":$allBooks,"authors":$authors}""") + val library = parseJson(s"""{"book":$allBooksJson,"authors":$authors}""") - JsonPath.query("""$.authors[?(@.pseudo=='Tolkien')].name""", library) should findElements(text("J. R. R. Tolkien")) + JsonPath.query("""$.authors[?(@.pseudo=='Tolkien')].name""", library) should findElements("J. R. R. Tolkien") - JsonPath.query("""$.book[?(@.author==$.authors[?(@.pseudo=='Tolkien')].name)].title""", library) should findElements(text("The Lord of the Rings")) + JsonPath.query("""$.book[?(@.author==$.authors[?(@.pseudo=='Tolkien')].name)].title""", library) should findElements("The Lord of the Rings") JsonPath.query("""$.book[?(@.author==$.authors[?(@.pseudo=='Hugo')].name)].title""", library) should findElements() } it should "honor current object" in { - JsonPath.query("""$..vegetable[?(@.color=='green')].name""", parseJson(veggies)) should findOrderedElements(text("peas")) + JsonPath.query("""$..vegetable[?(@.color=='green')].name""", parseJson(veggies)) should findOrderedElements("peas") } it should "not mess up with node with the same name at different depths in the hierarchy" in { val json = """{"foo":{"nico":{"nico":42}}}""" - JsonPath.query("""$..foo[?(@.nico)]""", parseJson(json)) should findElements(parseJson("""{"nico":{"nico":42}}""")) + JsonPath.query("""$..foo[?(@.nico)]""", parseJson(json)) should findElements(Map("nico" -> Map("nico" -> 42))) } "`null` elements" should "be correctly handled" in { // val fooNull = parseJson("""{"foo":null}""") - // JsonPath.query("$.foo", fooNull) should findElements(NullNode.instance) + // JsonPath.query("$.foo", fooNull) should findElements(null.instance) // JsonPath.query("$.foo.bar", fooNull) should findElements() val arrayWithNull = parseJson("""{"foo":[1,null,3,"woot"]}""") - JsonPath.query("$.foo[?(@==null)]", arrayWithNull) should findElements(nullNode) + JsonPath.query("$.foo[?(@==null)]", arrayWithNull) should findElements(null) // JsonPath.query("$.foo[?(@>=null)]", arrayWithNull) should findElements() - // JsonPath.query("$.foo[?(@>=0.5)]", arrayWithNull) should findOrderedElements(int(1), int(3)) + // JsonPath.query("$.foo[?(@>=0.5)]", arrayWithNull) should findOrderedElements(1, 3) } "empty String value" should "be correctly handled" in { val emptyStringValue = parseJson("""[{"foo": "bar", "baz": ""}]""") - JsonPath.query("$[?(@.baz == '')].foo", emptyStringValue) should findElements(text("bar")) + JsonPath.query("$[?(@.baz == '')].foo", emptyStringValue) should findElements("bar") } /// Goessner reference examples /////////////////////////////////////////// @@ -1047,132 +1042,133 @@ class JsonPathSpec extends FlatSpec with Matchers with JsonPathMatchers { "Goessner examples" should "work with finding all the authors" in { JsonPath.query("$.store.book[*].author", goessnerJson) should findOrderedElements( - text("Nigel Rees"), text("Evelyn Waugh"), text("Herman Melville"), text("J. R. R. Tolkien") + "Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien" ) JsonPath.query("$..author", goessnerJson) should findOrderedElements( - text("Nigel Rees"), text("Evelyn Waugh"), text("Herman Melville"), text("J. R. R. Tolkien") + "Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien" ) } it should "work with getting the whole store" in { JsonPath.query("$..book.*", goessnerJson) should findElements() - JsonPath.query("$.store.*", goessnerJson) should findOrderedElements(parseJson(allBooks), parseJson(bicycle)) + JsonPath.query("$.store.*", goessnerJson) should findOrderedElements(List(book1Map, book2Map, book3Map, book4Map), bicycleMap) } it should "work with getting all prices" in { JsonPath.query("$.store..price", goessnerJson) should findOrderedElements( - double(8.95), double(12.99), double(8.99), double(22.99), double(19.95) + 8.95, 12.99, 8.99, 22.99, 19.95 ) } it should "work with getting books by indices" in { - JsonPath.query("$..book[2]", goessnerJson) should findOrderedElements(parseJson(book3)) - JsonPath.query("$..book[-1:]", goessnerJson) should findOrderedElements(parseJson(book4)) - JsonPath.query("$..book[0,1]", goessnerJson) should findOrderedElements(parseJson(book1), parseJson(book2)) - JsonPath.query("$..book[:2]", goessnerJson) should findOrderedElements(parseJson(book1), parseJson(book2)) + JsonPath.query("$..book[2]", goessnerJson) should findOrderedElements(book3Map) + JsonPath.query("$..book[-1:]", goessnerJson) should findOrderedElements(book4Map) + JsonPath.query("$..book[0,1]", goessnerJson) should findOrderedElements(book1Map, book2Map) + JsonPath.query("$..book[:2]", goessnerJson) should findOrderedElements(book1Map, book2Map) } it should "allow to get everything" in { JsonPath.query("$..*", goessnerJson) should findElements( - goessnerJson, - parseJson(allStore), - parseJson(bicycle), - text("red"), - double(19.95), - parseJson(allBooks), - parseJson(book1), - parseJson(book2), - parseJson(book3), - parseJson(book4), - text("Nigel Rees"), - text("Sayings of the Century"), - text("reference"), - double(8.95), - text("Evelyn Waugh"), - text("Sword of Honour"), - text("fiction"), - double(12.99), - text("Herman Melville"), - text("Moby Dick"), - text("fiction"), - double(8.99), - text("0-553-21311-3"), - text("J. R. R. Tolkien"), - text("The Lord of the Rings"), - text("fiction"), - double(22.99), - text("0-395-19395-8") + goessnerMap, + allStoreMap, + bicycleMap, + "red", + 19.95, + allBooksList, + book1Map, + book2Map, + book3Map, + book4Map, + "Nigel Rees", + "Sayings of the Century", + "reference", + 8.95, + "Evelyn Waugh", + "Sword of Honour", + "fiction", + 12.99, + "Herman Melville", + "Moby Dick", + "fiction", + 8.99, + "0-553-21311-3", + "J. R. R. Tolkien", + "The Lord of the Rings", + "fiction", + 22.99, + "0-395-19395-8" ) } it should "work with subscript filters" in { JsonPath.query("$..book[?(@.isbn)]", goessnerJson) should findOrderedElements( - parseJson(book3), parseJson(book4) + book3Map, book4Map ) JsonPath.query("$..book[?(@.isbn)].title", goessnerJson) should findOrderedElements( - text("Moby Dick"), text("The Lord of the Rings") + "Moby Dick", "The Lord of the Rings" ) JsonPath.query("$.store.book[?(@.category == 'fiction')].title", goessnerJson) should findOrderedElements( - text("Sword of Honour"), text("Moby Dick"), text("The Lord of the Rings") + "Sword of Honour", "Moby Dick", "The Lord of the Rings" ) JsonPath.query("$.store.book[?(@.price < 20 && @.price > 8.96)].title", goessnerJson) should findOrderedElements( - text("Sword of Honour"), text("Moby Dick") + "Sword of Honour", "Moby Dick" ) } "Recursive" should "honor filters directly on root" in { - JsonPath.query("$[?(@.id==19434 && @.foo==1)].foo", parseJson(json)) should findOrderedElements(int(1)) + JsonPath.query("$[?(@.id==19434 && @.foo==1)].foo", parseJson(json)) should findOrderedElements(1) } it should "honor recursive filters from root" in { - JsonPath.query("$..*[?(@.id==19434 && @.foo==1)].foo", parseJson(json)) should findOrderedElements(int(1)) + JsonPath.query("$..*[?(@.id==19434 && @.foo==1)].foo", parseJson(json)) should findOrderedElements(1) } "Searches" should "honor recursive field + recursive filter + recursive field" in { - JsonPath.query("""$..changes..[?(@.selectmode)]..id""", parseJson(searches)) should findOrderedElements(text("1012")) + JsonPath.query("""$..changes..[?(@.selectmode)]..id""", parseJson(searches)) should findOrderedElements("1012") } it should "honor recursive filter from root + recursive field" in { - JsonPath.query("""$..[?(@.selectmode)]..id""", parseJson(searches)) should findOrderedElements(text("1012")) + JsonPath.query("""$..[?(@.selectmode)]..id""", parseJson(searches)) should findOrderedElements("1012") } it should "honor recursive filter from root + field" in { - JsonPath.query("""$..[?(@.selectmode)].id""", parseJson(searches)) should findOrderedElements(text("1012")) + JsonPath.query("""$..[?(@.selectmode)].id""", parseJson(searches)) should findOrderedElements("1012") } it should "honor recursive filter with wildcard from root + field" in { - JsonPath.query("""$..*[?(@.selectmode)].id""", parseJson(searches)) should findOrderedElements(text("1012")) + JsonPath.query("""$..*[?(@.selectmode)].id""", parseJson(searches)) should findOrderedElements("1012") } it should "honor recursive filter with wildcard from root + recursive field" in { - JsonPath.query("""$..*[?(@.selectmode)]..id""", parseJson(searches)) should findOrderedElements(text("1012")) + JsonPath.query("""$..*[?(@.selectmode)]..id""", parseJson(searches)) should findOrderedElements("1012") } it should "honor deep array access filter" in { - JsonPath.query("""$..changes[?(@[2][1].selectmode)][2][1].id""", parseJson(searches)) should findOrderedElements(text("1012")) + JsonPath.query("""$..changes[?(@[2][1].selectmode)][2][1].id""", parseJson(searches)) should findOrderedElements("1012") } it should "work fine when filter contains parens" in { - JsonPath.query("""$..*[?(@.message1=='bar(baz)')].id""", parseJson(valuesWithParensAndBraces)) should findOrderedElements(int(1)) + JsonPath.query("""$..*[?(@.message1=='bar(baz)')].id""", parseJson(valuesWithParensAndBraces)) should findOrderedElements(1) } it should "work fine when filter contains square braces" in { - JsonPath.query("""$..*[?(@.message2=='bar[baz]')].id""", parseJson(valuesWithParensAndBraces)) should findOrderedElements(int(1)) + JsonPath.query("""$..*[?(@.message2=='bar[baz]')].id""", parseJson(valuesWithParensAndBraces)) should findOrderedElements(1) } } trait JsonPathMatchers { - class OrderedElementsMatcher(expected: Traversable[JsValue]) extends Matcher[Either[JPError, Iterator[JsValue]]] { - override def apply(input: Either[JPError, Iterator[JsValue]]): MatchResult = + class OrderedElementsMatcher(expected: Traversable[Any]) extends Matcher[Either[JPError, Iterator[Any]]] { + override def apply(input: Either[JPError, Iterator[Any]]): MatchResult = input match { case Right(it) => - val seq = it.toVector + val actual = it.toList + val expectedList = expected.toList MatchResult( - seq == expected, - s"$seq does not contains the same elements as $expected", - s"$seq is equal to $expected but it shouldn't" + actual == expectedList, + s"$actual does not contains the same elements as $expectedList", + s"$actual is equal to $expected but it shouldn't" ) case Left(e) => MatchResult( matches = false, @@ -1182,10 +1178,10 @@ trait JsonPathMatchers { } } - def findOrderedElements(expected: JsValue*) = new OrderedElementsMatcher(expected) + def findOrderedElements(expected: Any*) = new OrderedElementsMatcher(expected) - class ElementsMatcher(expected: Traversable[JsValue]) extends Matcher[Either[JPError, Iterator[JsValue]]] { - override def apply(input: Either[JPError, Iterator[JsValue]]): MatchResult = + class ElementsMatcher(expected: Traversable[Any]) extends Matcher[Either[JPError, Iterator[Any]]] { + override def apply(input: Either[JPError, Iterator[Any]]): MatchResult = input match { case Right(it) => val actualSeq = it.toVector @@ -1212,5 +1208,5 @@ trait JsonPathMatchers { } } - def findElements(expected: JsValue*) = new ElementsMatcher(expected) + def findElements(expected: Any*) = new ElementsMatcher(expected) } diff --git a/src/test/scala/io/gatling/jsonpath/ComparisonOperatorsSpec.scala b/core/src/test/scala/io/gatling/jsonpath/ComparisonOperatorsSpec.scala similarity index 81% rename from src/test/scala/io/gatling/jsonpath/ComparisonOperatorsSpec.scala rename to core/src/test/scala/io/gatling/jsonpath/ComparisonOperatorsSpec.scala index 3d920d5..f5ebfc0 100644 --- a/src/test/scala/io/gatling/jsonpath/ComparisonOperatorsSpec.scala +++ b/core/src/test/scala/io/gatling/jsonpath/ComparisonOperatorsSpec.scala @@ -20,7 +20,6 @@ import org.scalacheck.Arbitrary import org.scalacheck.Arbitrary.arbitrary import org.scalatest.prop.GeneratorDrivenPropertyChecks import org.scalatest.{ FlatSpec, Matchers } -import play.api.libs.json.{ JsBoolean, JsNumber, JsString } class ComparisonOperatorsSpec extends FlatSpec @@ -29,8 +28,8 @@ class ComparisonOperatorsSpec "comparison operators" should "return false if types aren't compatible" in { forAll(arbitrary[String], arbitrary[Int]) { (string, int) => - val lhn = JsString(string) - val rhn = JsNumber(BigDecimal(int)) + val lhn = StringValue(string) + val rhn = LongValue(int) LessOperator(lhn, rhn) shouldBe false GreaterOperator(lhn, rhn) shouldBe false LessOrEqOperator(lhn, rhn) shouldBe false @@ -38,8 +37,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Boolean], arbitrary[String]) { (bool, string) => - val lhn = JsBoolean(bool) - val rhn = JsString(string) + val lhn = BooleanValue(bool) + val rhn = StringValue(string) LessOperator(lhn, rhn) shouldBe false GreaterOperator(lhn, rhn) shouldBe false LessOrEqOperator(lhn, rhn) shouldBe false @@ -47,8 +46,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Int], arbitrary[String]) { (int, string) => - val lhn = JsNumber(BigDecimal(int)) - val rhn = JsString(string) + val lhn = LongValue(int) + val rhn = StringValue(string) LessOperator(lhn, rhn) shouldBe false GreaterOperator(lhn, rhn) shouldBe false LessOrEqOperator(lhn, rhn) shouldBe false @@ -58,8 +57,8 @@ class ComparisonOperatorsSpec it should "properly compare Strings" in { forAll(arbitrary[String], arbitrary[String]) { (val1, val2) => - val lhn = JsString(val1) - val rhn = JsString(val2) + val lhn = StringValue(val1) + val rhn = StringValue(val2) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -69,8 +68,8 @@ class ComparisonOperatorsSpec it should "properly compare Booleans" in { forAll(arbitrary[Boolean], arbitrary[Boolean]) { (val1, val2) => - val lhn = JsBoolean(val1) - val rhn = JsBoolean(val2) + val lhn = BooleanValue(val1) + val rhn = BooleanValue(val2) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -80,8 +79,8 @@ class ComparisonOperatorsSpec it should "properly compare Int with other numeric types" in { forAll(arbitrary[Int], arbitrary[Int]) { (val1, val2) => - val lhn = JsNumber(BigDecimal(val1)) - val rhn = JsNumber(BigDecimal(val2)) + val lhn = LongValue(val1) + val rhn = LongValue(val2) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -89,8 +88,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Int], arbitrary[Long]) { (val1, val2) => - val lhn = JsNumber(BigDecimal(val1)) - val rhn = JsNumber(BigDecimal(val2)) + val lhn = LongValue(val1) + val rhn = LongValue(val2) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -98,8 +97,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Int], arbitrary[Double]) { (val1, val2) => - val lhn = JsNumber(BigDecimal(val1)) - val rhn = JsNumber(BigDecimal(val2)) + val lhn = LongValue(val1) + val rhn = DoubleValue(val2) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -107,8 +106,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Int], arbitrary[Float]) { (val1, val2) => - val lhn = JsNumber(BigDecimal(val1)) - val rhn = JsNumber(BigDecimal(val2)) + val lhn = LongValue(val1) + val rhn = DoubleValue(val2) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -118,8 +117,8 @@ class ComparisonOperatorsSpec it should "properly compare Long with other numeric types" in { forAll(arbitrary[Long], arbitrary[Int]) { (val1, val2) => - val lhn = JsNumber(BigDecimal(val1)) - val rhn = JsNumber(BigDecimal(val2)) + val lhn = LongValue(val1) + val rhn = LongValue(val2) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -127,8 +126,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Long], arbitrary[Long]) { (val1, val2) => - val lhn = JsNumber(BigDecimal(val1)) - val rhn = JsNumber(BigDecimal(val2)) + val lhn = LongValue(val1) + val rhn = LongValue(val2) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -136,8 +135,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Long], arbitrary[Double]) { (val1, val2) => - val lhn = JsNumber(BigDecimal(val1)) - val rhn = JsNumber(BigDecimal(val2)) + val lhn = LongValue(val1) + val rhn = DoubleValue(val2) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -145,8 +144,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Long], arbitrary[Float]) { (val1, val2) => - val lhn = JsNumber(BigDecimal(val1)) - val rhn = JsNumber(BigDecimal(val2)) + val lhn = LongValue(val1) + val rhn = DoubleValue(val2) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -156,8 +155,8 @@ class ComparisonOperatorsSpec it should "properly compare Double with other numeric types" in { forAll(arbitrary[Double], arbitrary[Int]) { (val1, val2) => - val lhn = JsNumber(BigDecimal(val1)) - val rhn = JsNumber(BigDecimal(val2)) + val lhn = DoubleValue(val1) + val rhn = LongValue(val2) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -165,8 +164,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Double], arbitrary[Long]) { (val1, val2) => - val lhn = JsNumber(BigDecimal(val1)) - val rhn = JsNumber(BigDecimal(val2)) + val lhn = DoubleValue(val1) + val rhn = LongValue(val2) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -174,8 +173,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Double], arbitrary[Double]) { (val1, val2) => - val lhn = JsNumber(BigDecimal(val1)) - val rhn = JsNumber(BigDecimal(val2)) + val lhn = DoubleValue(val1) + val rhn = DoubleValue(val2) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -183,8 +182,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Double], arbitrary[Float]) { (val1, val2) => - val lhn = JsNumber(BigDecimal(val1)) - val rhn = JsNumber(BigDecimal(val2)) + val lhn = DoubleValue(val1) + val rhn = DoubleValue(val2) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -194,8 +193,8 @@ class ComparisonOperatorsSpec it should "properly compare Float with other numeric types" in { forAll(arbitrary[Float], arbitrary[Int]) { (val1, val2) => - val lhn = JsNumber(BigDecimal(val1)) - val rhn = JsNumber(BigDecimal(val2)) + val lhn = DoubleValue(val1) + val rhn = LongValue(val2) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -203,8 +202,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Float], arbitrary[Long]) { (val1, val2) => - val lhn = JsNumber(BigDecimal(val1)) - val rhn = JsNumber(BigDecimal(val2)) + val lhn = DoubleValue(val1) + val rhn = LongValue(val2) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -212,8 +211,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Float], arbitrary[Double]) { (val1, val2) => - val lhn = JsNumber(BigDecimal(val1)) - val rhn = JsNumber(BigDecimal(val2)) + val lhn = DoubleValue(val1) + val rhn = DoubleValue(val2) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) @@ -221,8 +220,8 @@ class ComparisonOperatorsSpec } forAll(arbitrary[Float], arbitrary[Float]) { (val1, val2) => - val lhn = JsNumber(BigDecimal(val1)) - val rhn = JsNumber(BigDecimal(val2)) + val lhn = DoubleValue(val1) + val rhn = DoubleValue(val2) LessOperator(lhn, rhn) shouldBe (val1 < val2) GreaterOperator(lhn, rhn) shouldBe (val1 > val2) LessOrEqOperator(lhn, rhn) shouldBe (val1 <= val2) diff --git a/src/test/scala/io/gatling/jsonpath/ParserSpec.scala b/core/src/test/scala/io/gatling/jsonpath/ParserSpec.scala similarity index 88% rename from src/test/scala/io/gatling/jsonpath/ParserSpec.scala rename to core/src/test/scala/io/gatling/jsonpath/ParserSpec.scala index 761c29a..5d2fbc5 100644 --- a/src/test/scala/io/gatling/jsonpath/ParserSpec.scala +++ b/core/src/test/scala/io/gatling/jsonpath/ParserSpec.scala @@ -16,11 +16,10 @@ package io.gatling.jsonpath -import org.scalatest.FlatSpec -import org.scalatest.matchers.{ Matcher, MatchResult } -import io.gatling.jsonpath.Parser._ import io.gatling.jsonpath.AST._ -import org.scalatest.Matchers +import io.gatling.jsonpath.Parser._ +import org.scalatest.matchers.{ MatchResult, Matcher } +import org.scalatest.{ FlatSpec, Matchers } class StringSpec extends FlatSpec with Matchers { "Fast string replacement" should "work as expected" in { @@ -31,7 +30,6 @@ class StringSpec extends FlatSpec with Matchers { } class ParserSpec extends FlatSpec with Matchers with ParsingMatchers { - "Field parsing" should "work with standard names" in { def shouldParseField(name: String) = { val field = Field(name) @@ -49,7 +47,7 @@ class ParserSpec extends FlatSpec with Matchers with ParsingMatchers { } it should "work with the root object" in { - parse(Parser.root, "$") should beParsedAs(RootNode) + parse(root, "$") should beParsedAs(RootNode) } it should "work when having multiple fields" in { @@ -174,13 +172,14 @@ class ParserSpec extends FlatSpec with Matchers with ParsingMatchers { } "Failures" should "be handled gracefully" in { - def gracefulFailure(query: String): Unit = - new Parser().compile(query) match { - case Parser.Failure(msg, _) => - info(s"""that's an expected failure for "$query": $msg""") - case other => - fail(s"""a Failure was expected but instead, for "$query" got: $other""") + def gracefulFailure(query: String): Unit = { + val result = new Parser().compile(query) + if (!result.successful) { + info(s"""that's an expected failure for "$query": $result""") + } else { + fail(s"""a Failure was expected but instead, for "$query" got: $result""") } + } gracefulFailure("") gracefulFailure("foo") @@ -212,63 +211,63 @@ class ParserSpec extends FlatSpec with Matchers with ParsingMatchers { // Check all supported ordering operators parse(subscriptFilter, "[?(@ == 2)]") should beParsedAs( - ComparisonFilter(EqOperator, SubQuery(List(CurrentNode)), FilterDirectValue.long(2)) + ComparisonFilter(EqOperator, SubQuery(List(CurrentNode)), FilterDirectValue(LongValue(2))) ) parse(subscriptFilter, "[?(@==2)]") should beParsedAs( - ComparisonFilter(EqOperator, SubQuery(List(CurrentNode)), FilterDirectValue.long(2)) + ComparisonFilter(EqOperator, SubQuery(List(CurrentNode)), FilterDirectValue(LongValue(2))) ) parse(subscriptFilter, "[?(@ <= 2)]") should beParsedAs( - ComparisonFilter(LessOrEqOperator, SubQuery(List(CurrentNode)), FilterDirectValue.long(2)) + ComparisonFilter(LessOrEqOperator, SubQuery(List(CurrentNode)), FilterDirectValue(LongValue(2))) ) parse(subscriptFilter, "[?(@<=2)]") should beParsedAs( - ComparisonFilter(LessOrEqOperator, SubQuery(List(CurrentNode)), FilterDirectValue.long(2)) + ComparisonFilter(LessOrEqOperator, SubQuery(List(CurrentNode)), FilterDirectValue(LongValue(2))) ) parse(subscriptFilter, "[?(@ >= 2)]") should beParsedAs( - ComparisonFilter(GreaterOrEqOperator, SubQuery(List(CurrentNode)), FilterDirectValue.long(2)) + ComparisonFilter(GreaterOrEqOperator, SubQuery(List(CurrentNode)), FilterDirectValue(LongValue(2))) ) parse(subscriptFilter, "[?(@>=2)]") should beParsedAs( - ComparisonFilter(GreaterOrEqOperator, SubQuery(List(CurrentNode)), FilterDirectValue.long(2)) + ComparisonFilter(GreaterOrEqOperator, SubQuery(List(CurrentNode)), FilterDirectValue(LongValue(2))) ) parse(subscriptFilter, "[?(@ < 2)]") should beParsedAs( - ComparisonFilter(LessOperator, SubQuery(List(CurrentNode)), FilterDirectValue.long(2)) + ComparisonFilter(LessOperator, SubQuery(List(CurrentNode)), FilterDirectValue(LongValue(2))) ) parse(subscriptFilter, "[?(@<2)]") should beParsedAs( - ComparisonFilter(LessOperator, SubQuery(List(CurrentNode)), FilterDirectValue.long(2)) + ComparisonFilter(LessOperator, SubQuery(List(CurrentNode)), FilterDirectValue(LongValue(2))) ) parse(subscriptFilter, "[?(@ > 2)]") should beParsedAs( - ComparisonFilter(GreaterOperator, SubQuery(List(CurrentNode)), FilterDirectValue.long(2)) + ComparisonFilter(GreaterOperator, SubQuery(List(CurrentNode)), FilterDirectValue(LongValue(2))) ) parse(subscriptFilter, "[?(@>2)]") should beParsedAs( - ComparisonFilter(GreaterOperator, SubQuery(List(CurrentNode)), FilterDirectValue.long(2)) + ComparisonFilter(GreaterOperator, SubQuery(List(CurrentNode)), FilterDirectValue(LongValue(2))) ) parse(subscriptFilter, "[?(@ == true)]") should beParsedAs( - ComparisonFilter(EqOperator, SubQuery(List(CurrentNode)), FilterDirectValue.True) + ComparisonFilter(EqOperator, SubQuery(List(CurrentNode)), FilterDirectValue(BooleanValue(true))) ) parse(subscriptFilter, "[?(@==true)]") should beParsedAs( - ComparisonFilter(EqOperator, SubQuery(List(CurrentNode)), FilterDirectValue.True) + ComparisonFilter(EqOperator, SubQuery(List(CurrentNode)), FilterDirectValue(BooleanValue(true))) ) parse(subscriptFilter, "[?(@ != false)]") should beParsedAs( - ComparisonFilter(NotEqOperator, SubQuery(List(CurrentNode)), FilterDirectValue.False) + ComparisonFilter(NotEqOperator, SubQuery(List(CurrentNode)), FilterDirectValue(BooleanValue(false))) ) parse(subscriptFilter, "[?(@!=false)]") should beParsedAs( - ComparisonFilter(NotEqOperator, SubQuery(List(CurrentNode)), FilterDirectValue.False) + ComparisonFilter(NotEqOperator, SubQuery(List(CurrentNode)), FilterDirectValue(BooleanValue(false))) ) parse(subscriptFilter, "[?(@ == null)]") should beParsedAs( - ComparisonFilter(EqOperator, SubQuery(List(CurrentNode)), FilterDirectValue.Null) + ComparisonFilter(EqOperator, SubQuery(List(CurrentNode)), FilterDirectValue(NullValue)) ) parse(subscriptFilter, "[?(@==null)]") should beParsedAs( - ComparisonFilter(EqOperator, SubQuery(List(CurrentNode)), FilterDirectValue.Null) + ComparisonFilter(EqOperator, SubQuery(List(CurrentNode)), FilterDirectValue(NullValue)) ) // Trickier Json path expressions parse(subscriptFilter, "[?(@.foo == 2)]") should beParsedAs( - ComparisonFilter(EqOperator, SubQuery(List(CurrentNode, Field("foo"))), FilterDirectValue.long(2)) + ComparisonFilter(EqOperator, SubQuery(List(CurrentNode, Field("foo"))), FilterDirectValue(LongValue(2))) ) parse(subscriptFilter, "[?(true == @.foo)]") should beParsedAs( - ComparisonFilter(EqOperator, FilterDirectValue.True, SubQuery(List(CurrentNode, Field("foo")))) + ComparisonFilter(EqOperator, FilterDirectValue(BooleanValue(true)), SubQuery(List(CurrentNode, Field("foo")))) ) parse(subscriptFilter, "[?(2 == @['foo'])]") should beParsedAs( - ComparisonFilter(EqOperator, FilterDirectValue.long(2), SubQuery(List(CurrentNode, Field("foo")))) + ComparisonFilter(EqOperator, FilterDirectValue(LongValue(2)), SubQuery(List(CurrentNode, Field("foo")))) ) // Allow reference to the root object @@ -278,17 +277,17 @@ class ParserSpec extends FlatSpec with Matchers with ParsingMatchers { new Parser().compile("$['points'][?(@['y'] >= 3)].id").get should be(RootNode :: Field("points") - :: ComparisonFilter(GreaterOrEqOperator, SubQuery(List(CurrentNode, Field("y"))), FilterDirectValue.long(3)) + :: ComparisonFilter(GreaterOrEqOperator, SubQuery(List(CurrentNode, Field("y"))), FilterDirectValue(LongValue(3))) :: Field("id") :: Nil) new Parser().compile("$.points[?(@['id']=='i4')].x").get should be(RootNode :: Field("points") - :: ComparisonFilter(EqOperator, SubQuery(List(CurrentNode, Field("id"))), FilterDirectValue.string("i4")) + :: ComparisonFilter(EqOperator, SubQuery(List(CurrentNode, Field("id"))), FilterDirectValue(StringValue("i4"))) :: Field("x") :: Nil) new Parser().compile("""$.points[?(@['id']=="i4")].x""").get should be(RootNode :: Field("points") - :: ComparisonFilter(EqOperator, SubQuery(List(CurrentNode, Field("id"))), FilterDirectValue.string("i4")) + :: ComparisonFilter(EqOperator, SubQuery(List(CurrentNode, Field("id"))), FilterDirectValue(StringValue("i4"))) :: Field("x") :: Nil) } @@ -337,7 +336,7 @@ class ParserSpec extends FlatSpec with Matchers with ParsingMatchers { BooleanFilter( OrOperator, HasFilter(SubQuery(List(CurrentNode, Field("foo")))), - ComparisonFilter(LessOrEqOperator, SubQuery(List(CurrentNode, Field("bar"))), FilterDirectValue.long(2)) + ComparisonFilter(LessOrEqOperator, SubQuery(List(CurrentNode, Field("bar"))), FilterDirectValue(LongValue(2))) ) ) } @@ -346,19 +345,19 @@ class ParserSpec extends FlatSpec with Matchers with ParsingMatchers { trait ParsingMatchers { class SuccessBeMatcher[+T <: AstToken](expected: T) extends Matcher[Parser.ParseResult[AstToken]] { - def apply(left: Parser.ParseResult[AstToken]): MatchResult = { - left match { - case Parser.Success(res, _) => MatchResult( + def apply(result: Parser.ParseResult[AstToken]): MatchResult = result match { + case Success(res, _) => + MatchResult( expected == res, s"$res is not equal to expected value $expected", s"$res is equal to $expected but it shouldn't be" ) - case Parser.NoSuccess(msg, _) => MatchResult( + case NoSuccess(msg, _) => + MatchResult( matches = false, s"parsing issue, $msg", s"parsing issue, $msg" ) - } } } diff --git a/jackson/build.sbt b/jackson/build.sbt new file mode 100644 index 0000000..d53a825 --- /dev/null +++ b/jackson/build.sbt @@ -0,0 +1 @@ +name := "jsonpath-jackson" diff --git a/jackson/dependencies.sbt b/jackson/dependencies.sbt new file mode 100644 index 0000000..7b8f253 --- /dev/null +++ b/jackson/dependencies.sbt @@ -0,0 +1,3 @@ +libraryDependencies += "com.fasterxml.jackson.core" % "jackson-databind" % "2.9.9" +libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test" +libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.14.0" % "test" diff --git a/jackson/src/main/scala/io/gatling/jsonpath/jackson/JsonPath.scala b/jackson/src/main/scala/io/gatling/jsonpath/jackson/JsonPath.scala new file mode 100644 index 0000000..8d8458c --- /dev/null +++ b/jackson/src/main/scala/io/gatling/jsonpath/jackson/JsonPath.scala @@ -0,0 +1,63 @@ +/* + * Copyright 2011-2019 GatlingCorp (https://gatling.io) + * + * 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 io.gatling.jsonpath.jackson + +import scala.collection.JavaConverters._ +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.node._ +import io.gatling.jsonpath._ + +object JsonPath extends BaseJsonPath[JsonNode] { + override protected def mapJsonObject(jsonObject: JsonNode): JsonElement[_] = JacksonElement(jsonObject) +} + +private object JacksonElement { + + def apply(value: JsonNode): JsonElement[_] = value match { + case obj: ObjectNode => JacksonObjectElement(obj) + case array: ArrayNode => JacksonArrayElement(array) + case num: NumericNode if num.isIntegralNumber => LongValue(num.longValue()) + case num: NumericNode => DoubleValue(num.doubleValue()) + case bool: BooleanNode => BooleanValue(bool.booleanValue()) + case str: TextNode => StringValue(str.textValue()) + case _: NullNode => NullValue + } + + case class JacksonObjectElement(obj: ObjectNode) extends ObjectElement { + override def contains(key: String): Boolean = obj.get(key) != null + + override def apply(key: String): JsonElement[_] = JacksonElement(obj.get(key)) + + override def values: Iterator[(String, JsonElement[_])] = + obj.fields().asScala.map(entry => (entry.getKey, JacksonElement(entry.getValue))) + + override def size: Int = obj.size + + override def isEmpty: Boolean = obj.size == 0 + } + + case class JacksonArrayElement(array: ArrayNode) extends ArrayElement { + override def values: Iterator[JsonElement[_]] = array.iterator.asScala.map(JacksonElement(_)) + + override def apply(index: Int): JsonElement[_] = JacksonElement(array.get(index)) + + override def size: Int = array.size + + override def isEmpty: Boolean = array.size == 0 + } + +} diff --git a/jackson/src/test/scala/io/gatling/jsonpath/jackson/JsonPathSpec.scala b/jackson/src/test/scala/io/gatling/jsonpath/jackson/JsonPathSpec.scala new file mode 100644 index 0000000..05990ca --- /dev/null +++ b/jackson/src/test/scala/io/gatling/jsonpath/jackson/JsonPathSpec.scala @@ -0,0 +1,29 @@ +/* + * Copyright 2011-2019 GatlingCorp (https://gatling.io) + * + * 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 io.gatling.jsonpath.jackson + +import com.fasterxml.jackson.databind.{ JsonNode, ObjectMapper } +import io.gatling.jsonpath.{ BaseJsonPath, BaseJsonPathSpec } + +class JsonPathSpec extends BaseJsonPathSpec[JsonNode] { + override def parseJson(s: String): JsonNode = JsonPathSpec.mapper.readValue(s, classOf[JsonNode]) + override def JsonPath: BaseJsonPath[JsonNode] = io.gatling.jsonpath.jackson.JsonPath +} + +object JsonPathSpec { + val mapper: ObjectMapper = new ObjectMapper() +} diff --git a/play/build.sbt b/play/build.sbt new file mode 100644 index 0000000..86cf405 --- /dev/null +++ b/play/build.sbt @@ -0,0 +1 @@ +name := "jsonpath-play" diff --git a/play/dependencies.sbt b/play/dependencies.sbt new file mode 100644 index 0000000..a90864e --- /dev/null +++ b/play/dependencies.sbt @@ -0,0 +1,3 @@ +libraryDependencies += "com.typesafe.play" %% "play-json" % "2.7.4" +libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test" +libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.14.0" % "test" diff --git a/play/src/main/scala/io/gatling/jsonpath/play/JsonPath.scala b/play/src/main/scala/io/gatling/jsonpath/play/JsonPath.scala new file mode 100644 index 0000000..a5ffc90 --- /dev/null +++ b/play/src/main/scala/io/gatling/jsonpath/play/JsonPath.scala @@ -0,0 +1,54 @@ +/* + * Copyright 2011-2019 GatlingCorp (https://gatling.io) + * + * 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 io.gatling.jsonpath.play + +import play.api.libs.json._ +import io.gatling.jsonpath._ + +object JsonPath extends BaseJsonPath[JsValue] { + override protected def mapJsonObject(jsonObject: JsValue): JsonElement[_] = PlayJsonElement(jsonObject) +} + +private object PlayJsonElement { + + def apply(value: JsValue): JsonElement[_] = value match { + case obj: JsObject => PlayObjectElement(obj) + case array: JsArray => PlayArrayElement(array) + case JsNumber(num) if num.isValidLong => LongValue(num.longValue()) + case JsNumber(num) => DoubleValue(num.doubleValue()) + case JsBoolean(bool) => BooleanValue(bool) + case JsString(str) => StringValue(str) + case JsNull => NullValue + } + + case class PlayObjectElement(obj: JsObject) extends ObjectElement { + override def contains(key: String): Boolean = obj.keys.contains(key) + override def apply(key: String): JsonElement[_] = PlayJsonElement(obj.value(key)) + override def values: Iterator[(String, JsonElement[_])] = + obj.value.iterator.map { case (key, value) => (key, PlayJsonElement(value)) } + override def size: Int = obj.value.size + override def isEmpty: Boolean = obj.value.isEmpty + } + + case class PlayArrayElement(array: JsArray) extends ArrayElement { + override def values: Iterator[JsonElement[_]] = array.value.iterator.map(PlayJsonElement(_)) + override def apply(index: Int): JsonElement[_] = PlayJsonElement(array.value(index)) + override def size: Int = array.value.size + override def isEmpty: Boolean = array.value.isEmpty + } + +} diff --git a/play/src/test/scala/io/gatling/jsonpath/play/JsonPathSpec.scala b/play/src/test/scala/io/gatling/jsonpath/play/JsonPathSpec.scala new file mode 100644 index 0000000..79ab847 --- /dev/null +++ b/play/src/test/scala/io/gatling/jsonpath/play/JsonPathSpec.scala @@ -0,0 +1,25 @@ +/* + * Copyright 2011-2019 GatlingCorp (https://gatling.io) + * + * 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 io.gatling.jsonpath.play + +import io.gatling.jsonpath.{ BaseJsonPath, BaseJsonPathSpec } +import play.api.libs.json.{ JsValue, Json } + +class JsonPathSpec extends BaseJsonPathSpec[JsValue] { + override def parseJson(s: String): JsValue = Json.parse(s) + override def JsonPath: BaseJsonPath[JsValue] = io.gatling.jsonpath.play.JsonPath +} From c834373c82e26c681ccba27806c1ce55e1d093d9 Mon Sep 17 00:00:00 2001 From: Aleksandr Chermenin Date: Wed, 26 Jun 2019 17:42:04 +0500 Subject: [PATCH 3/4] Updated version --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index c92acc8..d8a83a9 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.7.0" \ No newline at end of file +version in ThisBuild := "0.8.0-SNAPSHOT" From ec7edbdae484c6613cf95ae98e1f73444e8ceb9f Mon Sep 17 00:00:00 2001 From: Aleksandr Chermenin Date: Wed, 26 Jun 2019 17:49:42 +0500 Subject: [PATCH 4/4] Rebase some changes in ParserSpec test case --- .../test/scala/io/gatling/jsonpath/ParserSpec.scala | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/core/src/test/scala/io/gatling/jsonpath/ParserSpec.scala b/core/src/test/scala/io/gatling/jsonpath/ParserSpec.scala index 5d2fbc5..fd9be13 100644 --- a/core/src/test/scala/io/gatling/jsonpath/ParserSpec.scala +++ b/core/src/test/scala/io/gatling/jsonpath/ParserSpec.scala @@ -172,14 +172,13 @@ class ParserSpec extends FlatSpec with Matchers with ParsingMatchers { } "Failures" should "be handled gracefully" in { - def gracefulFailure(query: String): Unit = { - val result = new Parser().compile(query) - if (!result.successful) { - info(s"""that's an expected failure for "$query": $result""") - } else { - fail(s"""a Failure was expected but instead, for "$query" got: $result""") + def gracefulFailure(query: String): Unit = + new Parser().compile(query) match { + case Parser.Failure(msg, _) => + info(s"""that's an expected failure for "$query": $msg""") + case other => + fail(s"""a Failure was expected but instead, for "$query" got: $other""") } - } gracefulFailure("") gracefulFailure("foo")