早先为 Jackson 写的 Json-Path 支持

今天才发现 Jackson 其实是支持 Json-Path 的,但以前一直不知道,关键是 Jackson 的文档也没见提到。所以很久以前是自己给 Jackson 写了一个简陋的 Json-Path 支持类,这个是为测试代码用的,所以基本够用。想要更全面的功能可使用 https://github.com/jayway/JsonPath 这个项目。对于 Jackson 中如何使用 Json-Path, 我还会进一步研究下。


我写的 RichJsonNode 类是一个 Scala 版本,最早也写过一个 Java 版本,还有一个基于 GSON 的 Java 版本的。这里只贴出 Scala 版源码。支持的基本方法及语法如下:

a/b, a/b[2], a/b[1]/c, a[1]/b, $[0]

selectNode(path), selectString(path), selectInt(path), selectDouble(path), selectBollean(path), hasField(path) 和  arrayLength(path)

源码如下:
 1package cc.unmi<br/><br/>
 2import com.fasterxml.jackson.databind.JsonNode<br/><br/>
 3/**
 4 * JSON PATH, Inspired by XPath, need an exact path relative to root element
 5 * Can't start with "/", of course, won't support "//" fuzzy query
 6 * json-path examples:
 7 * <ul>
 8 * <li>a/b</li>
 9 * <li>a/b[2]</li>
10 * <li>a/b[1]/c</li>
11 * <li>a[1]/b</li>
12 * <li>$[0] if root element is an Array, can't use [0]/a</li>
13 * </ul>
14 */
15class RichJsonNode(json: JsonNode) {
16
17  def selectNode(jsonPath: String) = getTailElement(jsonPath)
18
19  def selectString(jsonPath: String) =
20    getTailElement(jsonPath).ensuring(t=>t.isNull||t.isTextual, "not a text node").textValue
21
22  def selectInt(jsonPath: String) =
23    getTailElement(jsonPath).ensuring(_.isInt, "not an int node").intValue()
24
25  def arrayLength(jsonPath: String) = getTailElement(jsonPath).size
26
27  def selectDouble(jsonPath: String) =
28    getTailElement(jsonPath).ensuring(_.isNumber, "not a double node").doubleValue()
29
30  def selectBoolean(jsonPath: String) =
31    getTailElement(jsonPath).ensuring(_.isBoolean, "not a boolean node").booleanValue
32
33  def isJsonNull(jsonPath: String) = getTailElement(jsonPath).isNull
34
35  def hasField(jsonPath: String): Boolean = {
36    try {
37      selectNode(jsonPath)
38      true
39    }
40    catch {
41      case _: Throwable => false
42    }
43  }
44
45  private def getTailElement(jsonPath: String): JsonNode = {
46    assume(jsonPath != null && jsonPath.trim.length > 0)
47    assume(!jsonPath.startsWith("/"), "doesn't support /a/b, or //b, path is relative")
48
49    jsonPath.split("/").foldLeft(json)((tmpJson, key) => {
50      if(key == "$"){
51        tmpJson
52      }else  if (key.contains("[")) {
53        val newKey = key.substring(0, key.indexOf('['))
54        val index = """\[\d+\]""".r.findFirstIn(key).get.replaceAll("""\[|\]""","").toInt
55        if (newKey == "$") {
56          tmpJson.get(index)
57        }
58        else {
59          tmpJson.get(newKey).get(index)
60        }
61      }
62      else {
63        tmpJson.get(key)
64      }
65    });
66
67  } ensuring( _ != null)
68
69}
70
71object RichJsonNode {
72  implicit def enrich(json: JsonNode):RichJsonNode = new RichJsonNode(json)
73}

因为做成了一个用于隐式转型的类,所以在 Scala 中使用起来比较简单
1import cc.unmi.RichJsonNode.enrich
2import com.fasterxml.jackson.databind.{JsonNode, ObjectMapper}
3
4object TestClient {
5    val jsonString = """{ "a": { "b": 123, "c": [1, 2, { "d": "456" } ] } }"""
6    val json = new ObjectMapper().readValue(jsonString, classOf[JsonNode])
7    System.out.println(json.selectInt("a/c[1]")) //2
8    System.out.println(json.selectString("a/b/c[2]/d") //456
9}

如果顶级是一个数组,如 [1, 2, {"a": 3}],那么可以用
1json.selectInt("$[0]") //1
2json.selectint("$[2]/a") //3

对于 Jackson 的 Json-Path 支持还正在研习当中,如果 Jackson 已足够好,我将摈弃这里的实现。 永久链接 https://yanbin.blog/jackson-json-path-support-before/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。