Search code examples
jsongroovyweb-scrapingmop

How to intercept map getProperty and list getAt?


I'm scraping external sources, mostly JSON. I'm using new JsonSlurper().parse(body) to parse them and I operate on them using constructs like def name = json.user[0].name. These being external, can change without notice, so I want to be able to detect this and log it somehow.

After reading a bit about the MOP I thought that I can change the appropriate methods of the maps and lists to log if the property is missing. I only want to do that the json object and on its properties recursively. The thing is, I don't know how to do that.

Or, is there a better way to accomplish all this?

[EDIT] For example if I get this 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'

However, if they change "owners" to "ownerInfo" the above access would throw NPE. What I want is intercept the access and do something (like log it in a special log, or whatever). I can also decide to throw a more specialized exception.

I don't want to catch NullPointerException, because it may be caused by some bug in my code instead of the changed data format. Besides, if they changed "firstName" to "givenName", but kept the "owners" name, I'd just get a null value, not NPE. Ideally I want to detect this case as well.

I also don't want to put a lot of if or evlis operators, if possible.

I actually managed to intercept that for maps:

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

I still can't find out how to do that for the list:

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

Solution

  • Try this

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

    I'm only guessing that it got lost because of multiple getAt() methods, so you have to define type. I also delegated to ArrayList's Java get() method since getAt() resulted in recursive calls.

    If you want to more control over all methods calls, you could always do this

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