Hi,
I want to create JSON from a semi-structured input. For better readability it would be great to write the JSON field name right after the corresponding captured string value, e.g. capture(…) ~> stringField("jsonAttributeName"). The action ~> operator expects a function, but there seems to be some limitation due to macro expansion, because the rule FruitShortInlineAttributeNames in the code below does not work, but some modified versions do. How to fix this, so that it can be written as short as in FruitShortInlineAttributeNames?
Side note: The resulting JSON object is constructed by maintaining a List of (String, JValue) tuples on parboileds value stack, which is used to create a JObject as last step.
import org.json4s._
import org.json4s.jackson.JsonMethods._
import org.parboiled2._
import scala.util.{Failure, Success}
object Main {
class TestParser(val input: ParserInput) extends Parser {
def stringField(name: String) = { (fields: List[JField], value: String) => (name, JString(value)) :: fields }
def intField(name: String) = { (fields: List[JField], value: Int) => (name, JInt(value)) :: fields }
def PushNonSpaceString = rule { capture(oneOrMore(!' ' ~ ANY)) }
def PushInt = rule { capture(oneOrMore(CharPredicate.Digit)) ~> { (_: String).toInt } }
// does not work: exception during macro expansion
def FruitShortInlineAttributeNames: Rule1[JObject] = rule {
push(List[JField]()) ~
"fruitName=" ~ PushNonSpaceString ~> stringField("name") ~ ' ' ~
"fruitWeight=" ~ PushInt ~> intField("weight") ~> { (fields: List[JField]) => JObject(fields) }
}
// works, but too much to write
def FruitInlineAttributeNames: Rule1[JObject] = rule {
push(List[JField]()) ~
"fruitName=" ~ PushNonSpaceString ~> { stringField("name")(_, _) } ~ ' ' ~
"fruitWeight=" ~ PushInt ~> { intField("weight")(_, _) } ~> { (fields: List[JField]) => JObject(fields) }
}
// this works, but JSON attribute name not inline
val nameField = stringField("name")
val weightField = intField("weight")
def FruitExternalFieldFunctions: Rule1[JObject] = rule {
push(List[JField]()) ~
"fruitName=" ~ PushNonSpaceString ~> nameField ~ ' ' ~
"fruitWeight=" ~ PushInt ~> weightField ~> { (fields: List[JField]) => JObject(fields) }
}
}
def main(args: Array[String]) {
val input = "fruitName=Apple fruitWeight=123"
val parser = new TestParser(input)
parser.FruitInlineAttributeNames.run() match {
case Success(obj: JObject) => println(compact(render(obj)))
case Failure(ex: Throwable) => println("error: "+ ex.toString)
}
}
}
build.sbt:
name := "parboiled-test"
scalaVersion := "2.11.7"
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature", "-Xfatal-warnings")
libraryDependencies ++= Seq(
"org.parboiled" %% "parboiled" % "2.1.0",
"org.json4s" %% "json4s-jackson" % "3.2.10"
)
Thank you!
Michael