All Downloads are FREE. Search and download functionalities are using the official Maven repository.

spray.json.lenses.JsonPathParser.scala Maven / Gradle / Ivy

The newest version!
package spray.json
package lenses

import java.lang.StringBuilder

import org.parboiled.Context
import org.parboiled.scala._
import org.parboiled.errors.{ ErrorUtils, ParsingException }

/**
 * A parser for json-path expression as specified here:
 * [[http://goessner.net/articles/JsonPath/]]
 */
object JsonPathParser extends Parser with BasicRules {
  def JsonPathExpr = rule { Path ~ EOI }

  def Path: Rule1[JsonPath.Path] = rule { Root ~ OptionalSelection }

  def Root: Rule1[JsonPath.Root.type] = rule {
    // we don't distinguish between '$' and '@'
    anyOf("$@") ~ push(JsonPath.Root)
  }

  def OptionalSelection: ReductionRule1[JsonPath.Path, JsonPath.Path] = rule {
    Projection ~~> JsonPath.Selection ~ OptionalSelection |
      EMPTY ~~> identity
  }

  def Projection: Rule1[JsonPath.Projection] = rule {
    "." ~ DotProjection |
      "[" ~ BracketProjection ~ "]"
  }

  def DotProjection: Rule1[JsonPath.Projection] = rule {
    ByFieldName
  }
  def AllElements = rule { "*" ~ push(JsonPath.AllElements) }
  def ByFieldName = rule { FieldName ~~> JsonPath.ByField }

  def BracketProjection: Rule1[JsonPath.Projection] = rule {
    Digits ~> (d ⇒ JsonPath.ByIndex(d.toInt)) |
      SingleQuotedString ~~> JsonPath.ByField |
      AllElements |
      "?(" ~ WhiteSpace ~ Predicate ~ WhiteSpace ~ ")" ~~> JsonPath.ByPredicate
  }

  def Predicate: Rule1[JsonPath.Predicate] = rule {
    Lt | Gt | Eq | Exists
  }
  def Eq: Rule1[JsonPath.Eq] = rule { op("==")(JsonPath.Eq) }
  def Lt: Rule1[JsonPath.Lt] = rule { op("<")(JsonPath.Lt) }
  def Gt: Rule1[JsonPath.Gt] = rule { op(">")(JsonPath.Gt) }
  def Exists: Rule1[JsonPath.Exists] = rule {
    Path ~~> JsonPath.Exists
  }

  def op[T](op: String)(cons: (JsonPath.Expr, JsonPath.SimpleExpr) ⇒ T) =
    Expr ~ WhiteSpace ~ op ~ WhiteSpace ~ SimpleExpr ~~> cons

  def Expr: Rule1[JsonPath.Expr] = rule {
    Path ~~> JsonPath.PathExpr |
      SimpleExpr
  }
  def SimpleExpr: Rule1[JsonPath.SimpleExpr] = rule {
    JsConstant ~~> JsonPath.Constant
  }
  def JsConstant: Rule1[JsValue] = rule {
    JsonNumber |
      SingleQuotedString ~~> (JsString(_))
  }

  val WhiteSpaceChars = " \n\r\t\f"
  def FieldName: Rule1[String] = rule {
    oneOrMore("a" - "z" | "A" - "Z" | "0" - "9" | anyOf("_-")) ~> identity
  }

  def SingleQuotedString: Rule1[String] =
    rule { "'" ~ push(new java.lang.StringBuilder) ~ zeroOrMore(!anyOf("'") ~ ("\\" ~ EscapedChar | NormalChar)) } ~ "'" ~~> (_.toString)

  /**
   * The main parsing method. Uses a ReportingParseRunner (which only reports the first error) for simplicity.
   */
  def apply(path: String): JsonPath.Path = apply(path.toCharArray)

  /**
   * The main parsing method. Uses a ReportingParseRunner (which only reports the first error) for simplicity.
   */
  def apply(path: Array[Char]): JsonPath.Path = {
    val parsingResult = ReportingParseRunner(JsonPathExpr).run(path)
    parsingResult.result.getOrElse {
      throw new ParsingException("Invalid JSON source:\n" + ErrorUtils.printParseErrors(parsingResult))
    }
  }
}

// a set of basic rules taken from the old spray-json parser
// see https://github.com/spray/spray-json/blob/v1.2.6/src/main/scala/spray/json/JsonParser.scala
trait BasicRules { _: Parser ⇒
  def EscapedChar = rule(
    anyOf("\"\\/") ~:% withContext(appendToSb(_)(_))
      | "b" ~ appendToSb('\b')
      | "f" ~ appendToSb('\f')
      | "n" ~ appendToSb('\n')
      | "r" ~ appendToSb('\r')
      | "t" ~ appendToSb('\t')
      | Unicode ~~% withContext((code, ctx) ⇒ appendToSb(code.asInstanceOf[Char])(ctx)))

  def NormalChar = rule { !anyOf("\"\\") ~ ANY ~:% (withContext(appendToSb(_)(_))) }
  def Unicode = rule { "u" ~ group(HexDigit ~ HexDigit ~ HexDigit ~ HexDigit) ~> (java.lang.Integer.parseInt(_, 16)) }

  def JsonNumber = rule { group(Integer ~ optional(Frac) ~ optional(Exp)) ~> (JsNumber(_)) ~ WhiteSpace }
  def Frac = rule { "." ~ Digits }
  def Exp = rule { ignoreCase("e") ~ optional(anyOf("+-")) ~ Digits }

  def Integer = rule { optional("-") ~ (("1" - "9") ~ Digits | Digit) }
  def Digits = rule { oneOrMore(Digit) }
  def Digit = rule { "0" - "9" }
  def HexDigit = rule { "0" - "9" | "a" - "f" | "A" - "F" }
  def WhiteSpace: Rule0 = rule { zeroOrMore(anyOf(" \n\r\t\f")) }

  def appendToSb(c: Char): Context[Any] ⇒ Unit = { ctx ⇒
    ctx.getValueStack.peek.asInstanceOf[StringBuilder].append(c)
    ()
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy