Search code examples
groovydynamic-programmingmetaclassexpandometaclass

Using Groovy metaClass to Implement Special Methods


I'm trying to modify the metaclass for JSONObject in Groovy to make it behave as much as possible like a regular Groovy map. When I implement methods in the metaclass, some of them are straightforward like JSONObject.metaClass.size in the example below. JSONObject has a length() method and I'm just wiring it up to a new size() method, but some methods have special meanings. For example, to get the subscript assignment to work, I had to override propertyMissing, not putAt. It looks like a lot of the collection operations like each, collect, findAll, etc are similar.

My first question is what special methods would I need to override in this case to make each() work? My second question is how would I figure out the answer myself? Is there a reference somewhere with methods that get special treatment from the MOP? I tried looking at the groovy-core source code, but there's a lot in there and I don't know where to start.

JSONObject.metaClass.propertyMissing = { String name, newValue -> delegate.put(name, newValue) }
JSONObject.metaClass.size = { -> delegate.length() }
JSONObject.metaClass.each = { cl -> delegate.keys().collectEntries{ [(it): delegate[it] ]}.each(cl) }

def json = new JSONObject()
json['a'] = 999
json.b    = 2.2
json['c'] = 'the letter C'
println json['a']            // Prints 999
println json['b']            // Prints 2.2
println json.c               // 'the letter C'
println json.size()         // Prints 3

//No signature of method: ... $__spock_feature_0_0_closure4.doCall() is applicable 
json.each{ k,v -> println "$k = $v"}

Solution

  • @Grab(group='org.json', module='json', version='20160810')
    
    import org.json.JSONArray
    import org.json.JSONObject
    
    JSONObject.metaClass.each={Closure c-> 
        delegate.keys().each{ k-> c(k, delegate.get(k) ) }  
    }
    
    JSONObject.metaClass.setProperty={String k, Object v-> 
        delegate.put(k,v) 
    }
    
    JSONObject.metaClass.getProperty={String k-> 
        delegate.get(k) 
    }
    
    JSONObject.metaClass.size = { -> delegate.length() }
    
    def json = new JSONObject()
    json['a'] = 999
    json.b    = 2.2
    json['c'] = 'the letter C'
    println json['a']            // Prints 999
    println json['b']            // Prints 2.2
    println json.c               // 'the letter C'
    println json.size()         // Prints 3
    
    //No signature of method: ... $__spock_feature_0_0_closure4.doCall() is applicable 
    json.each{ k,v -> println "$k = $v"}
    

    output:

    999
    2.2
    the letter C
    3
    a = 999
    b = 2.2
    c = the letter C