如何拦截map getProperty和list getAt?

How to intercept map getProperty and list getAt?

我正在抓取外部资源,主要是 JSON。我正在使用 new JsonSlurper().parse(body) 来解析它们,并使用 def name = json.user[0].name 等结构对它们进行操作。这些是外部的,可以在不通知的情况下更改,所以我希望能够检测到它并以某种方式记录它。

在阅读了一些关于 MOP 的内容后,我认为如果缺少 属性,我可以更改地图和列表的适当方法以进行记录。我只想递归地执行 json 对象及其属性。问题是,我不知道该怎么做。

或者,有没有更好的方法来完成这一切?

[编辑] 例如,如果我得到这个 JSON:

def json = '''{
  "owners": [
    {
      "firstName": "John",
      "lastName": "Smith"
    },
    {
      "firstName": "Jane",
      "lastName": "Smith"
    }
  ]
}'''

def data = new groovy.json.JsonSlurper().parse(json.bytes)
assert data.owners[0].firstName == 'John'

但是,如果他们将 "owners" 更改为 "ownerInfo",则上述访问将抛出 NPE。我想要的是拦截访问并做一些事情(比如将它记录在一个特殊的日志中,或者其他什么)。我也可以决定抛出一个更特殊的异常。

我不想捕获 NullPointerException,因为它可能是由我的代码中的某些错误而不是更改的数据格式引起的。此外,如果他们将 "firstName" 更改为 "givenName",但保留 "owners" 名称,我只会得到一个 null 值,而不是 NPE。理想情况下,我也想检测这种情况。

如果可能的话,我也不想放很多 if 或 evlis 运算符。

我实际上设法拦截了地图:

data.getMetaClass().getProperty = {name -> println ">>> $name"; delegate.get(name)}
assert data.owners // this prints ">>> owners"

我仍然不知道如何为列表执行此操作:

def owners = data.owners
owners.getMetaClass().getAt(o -> println "]]] $o"; delegate.getAt(o)}
assert owners[0] // this doesn't print anything

简短的回答是你不能用给定的例子来做到这一点。原因是 owners 对象是一个 java.util.ArrayList,而您正在对它调用 get(int index) 方法。 metaClass 对象特定于 Groovy,如果您有一个 Java 对象调用 Java 方法,它将不知道 [=15] =]. Here's a somewhat related question.

好消息是 和选项,但我不确定它是否适用于您的用例。您可以为此列表创建一个 Groovy 包装器对象,以便您可以捕获方法调用。

例如,您可以从这里更改您的代码

def owners = data.owners

至此

def owners = new GroovyList(data.owners)

然后创建这个新的 class

class GroovyList {
    private List list

    public GroovyList(List list) {
        this.list = list
    }

    public Object getAt(int index) {
        println "]]] $index"
        list.getAt(index)
    }
}

现在当你打电话时

owners[0]

你会得到输出

]]] 0
[firstName:John, lastName:Smith]

试试这个

owners.getMetaClass().getAt = { Integer o -> println "]]] $o"; delegate.get(o)}

我只是猜测它因为多个 getAt() 方法而丢失了,所以你必须定义类型。我还委托给 ArrayList 的 Java get() 方法,因为 getAt() 会导致递归调用。

如果您想更好地控制所有方法调用,您总是可以这样做

owners.getMetaClass().invokeMethod = { String methodName, Object[] args ->
    if (methodName == "getAt") {
        println "]]] $args"
    }
    return ArrayList.getMetaClass().getMetaMethod(methodName, args).invoke(delegate, args)
}