package cc.unmi
import com.fasterxml.jackson.databind.JsonNode
/**
* JSON PATH, Inspired by XPath, need an exact path relative to root element
* Can't start with "/", of course, won't support "//" fuzzy query
* json-path examples:
* <ul>
* <li>a/b</li>
* <li>a/b[2]</li>
* <li>a/b[1]/c</li>
* <li>a[1]/b</li>
* <li>$[0] if root element is an Array, can't use [0]/a</li>
* </ul>
*/
class RichJsonNode(json: JsonNode) {
def selectNode(jsonPath: String) = getTailElement(jsonPath)
def selectString(jsonPath: String) =
getTailElement(jsonPath).ensuring(t=>t.isNull||t.isTextual, "not a text node").textValue
def selectInt(jsonPath: String) =
getTailElement(jsonPath).ensuring(_.isInt, "not an int node").intValue()
def arrayLength(jsonPath: String) = getTailElement(jsonPath).size
def selectDouble(jsonPath: String) =
getTailElement(jsonPath).ensuring(_.isNumber, "not a double node").doubleValue()
def selectBoolean(jsonPath: String) =
getTailElement(jsonPath).ensuring(_.isBoolean, "not a boolean node").booleanValue
def isJsonNull(jsonPath: String) = getTailElement(jsonPath).isNull
def hasField(jsonPath: String): Boolean = {
try {
selectNode(jsonPath)
true
}
catch {
case _: Throwable => false
}
}
private def getTailElement(jsonPath: String): JsonNode = {
assume(jsonPath != null && jsonPath.trim.length > 0)
assume(!jsonPath.startsWith("/"), "doesn't support /a/b, or //b, path is relative")
jsonPath.split("/").foldLeft(json)((tmpJson, key) => {
if(key == "$"){
tmpJson
}else if (key.contains("[")) {
val newKey = key.substring(0, key.indexOf('['))
val index = """\[\d+\]""".r.findFirstIn(key).get.replaceAll("""\[|\]""","").toInt
if (newKey == "$") {
tmpJson.get(index)
}
else {
tmpJson.get(newKey).get(index)
}
}
else {
tmpJson.get(key)
}
});
} ensuring( _ != null)
}
object RichJsonNode {
implicit def enrich(json: JsonNode):RichJsonNode = new RichJsonNode(json)
}