I'd like to store some properties in a static closure and later access them during a method call:
class Person {
static someMap = { key1: "value1", key2: "value2" }
}
So how can I write a method within Person, which retrieves this stored data?
For the simple case, you're better off using a map.
If you really do want to evaluate it as a closure (possibly to create your own DSL), you'll need to change your syntax slightly as John points out. Here's one way to do it using a Builder class to evaluate the "something" closure within whatever is passed to the builder.
It uses groovy metaprogramming to intercept calls with method/property missing and to save them off:
class SomethingBuilder {
Map valueMap = [:]
SomethingBuilder(object) {
def callable = object.something
callable.delegate = this
callable.resolveStrategy = Closure.DELEGATE_FIRST
callable()
}
def propertyMissing(String name) {
return valueMap[name]
}
def propertyMissing(String name, value) {
valueMap[name] = value
}
def methodMissing(String name, args) {
if (args.size() == 1) {
valueMap[name] = args[0]
} else {
valueMap[name] = args
}
}
}
class Person {
static something = {
key1 "value1" // calls methodMissing("key1", ["value1"])
key2("value2") // calls methodMissing("key2", ["value2"])
key3 = "value3" // calls propertyMissing("key3", "value3")
key4 "foo", "bar", "baz" // calls methodMissing("key4", ["foo","bar","baz"])
}
}
def builder = new SomethingBuilder(new Person())
assert "value1" == builder."key1" // calls propertyMissing("key1")
assert "value2" == builder."key2" // calls propertyMissing("key2")
assert "value3" == builder."key3" // calls propertyMissing("key3")
assert ["foo", "bar", "baz"] == builder."key4" // calls propertyMissing("key4")
If they need to be initialized by a closure, instead of a map, then somebody's got to run the closure in order to pick up and record the values as they're set.
Your syntax there isn't valid. Remember closures are just anonymous methods. Your syntax looks like you're trying to define a map, but closures would need to call methods, set variables or return maps. e.g.
static someClosure = { key1 = "value1"; key2 = "value2" } // set variables
static someClosure = { key1 "value1"; key2 = "value2" } // call methods
static someClosure = { [key1: "value1", key2: "value2"] } // return a map
Then of course whoever's running the closure needs to have the right metaprogramming to record the results.
It sounds like what you really want is just to define a map.
static someMap = [key1: "value1", key2: "value2"]
This is what I came up with to extract static closure properties:
class ClosureProps {
Map props = [:]
ClosureProps(Closure c) {
c.delegate = this
c.each{"$it"()}
}
def methodMissing(String name, args) {
props[name] = args.collect{it}
}
def propertyMissing(String name) {
name
}
}
// Example
class Team {
static schema = {
table team
id teamID
roster column:playerID, cascade:[update,delete]
}
}
def c = new ClosureProps(Team.schema)
println c.props.table
Related
{
"map": {
"key1": [3,12,13,11],
"key2": [21,23],
"key3": [31,32,33]
}}
I have this JSON. similar to key1 or key2 I want to add new key- pair to this json using groovy. I am using JsonSlurper().
def mJson = new File(MAPPINGJSON).text;
def mJsonObject = parser.parseText(mJson);
def list= mJsonObject.map;
def keyFound= false;
for (item in list)
{
if (item.key == templateKey)
{
def values = item.value;
values.add(<some value>);
item.value= values;
keyFound = true;
break;
}
keyFound = false;
}
if(!keyFound)
{
println "Key not found";
// How to add new key pair?
}
list[templateKey] = [<some value>]by daggett is one way, but you can also use a one liner to do the trick.
def list= mJsonObject.map;
list.computeIfAbsent(templateKey, { [] }).add(templateValue)
It uses a function to provide the default value of the map.
I'm trying to find laconic and efficient way to replace placeholders with values at the Groovy Strings. But I can't find convenient solution for 2 cases:
When String with the placeholder and the value are defined at different classes.
When the String is passed as argument to a method, and should be replaced with local's variable value. Here is the illustration of 2 approaches I have tried:
class A {
static def strPlaceHolder = 'token = ${tokenValue}';
static def strRefPlaceHolder = "token = ${->tokenRef}";
}
class B {
def tokenRef = "token reference as field";
void parseGString(GString str) {
println str; //fails here. No property tokenRef for class: A. Though I've expected that "this" is B
}
void parseString(String str) {
def tokenValue = "token value as local variable";
println str; //I know why it doesn't work as required. But how to make something similar
}
}
new B().parseString(A.strPlaceHolder); //token = ${tokenValue}
new B().parseGString(A.strRefPlaceHolder); //fails,
You could replace your GString fields with closures and pass those closures to your methods. e.g.:
class A {
static def strPlaceHolder = { token -> "token = ${token}" }
}
class B {
def tokenRef = "token reference as field";
void parseGString(def closure) {
println closure(tokenRef)
}
void parseString(def closure) {
def tokenValue = "token value as local variable"
println closure(tokenValue)
}
}
new B().parseString(A.strPlaceHolder);
new B().parseGString(A.strPlaceHolder);
I get an object via some 3rd party api. I use a wrapper function to get it and then return a map from its properties:
wrapperFunc() {
def myObj = someapi.getblah().getSomeObect()
return [
aaa: myObj.aaa,
bbb: myObj.bbb,
ccc: myObj.ccc
]
}
Now I could manually go through EVERY property in the object like this, but is there an elegant groovy feature to dynamically build a map from the object's properties?
You could do something like this:
class Widget {
int width
int height
static void main(args) {
def obj = new Widget(width: 7, height: 9)
List<MetaProperty> metaProperties = obj.metaClass.properties
def props = [:]
for(MetaProperty mp : metaProperties) {
props[mp.name] = mp.getProperty(obj)
}
// props will look like [width:7, class:class demo.Widget, height:9]
}
}
This is basically a variant of #jeff-scott-brown's answer.
First, create a class that contains the Object-to-Map logic that uses the Groovy MetaClass to access a type's properties. findAll filters out the "class" property, which I assume you don't care about. The collectEntries line transforms each MetaProperty object into a Map entry.
class ElegantGroovyFeature {
static Map asType(Object o, Class m) {
if (m == Map) {
o.metaClass.properties
.findAll { it.getSetter() != null }
.collectEntries { prop -> [prop.name, prop.getProperty(o)] }
} else {
o.asType(m)
}
}
}
The extension class overrides the asType method, which corresponds to the as operator, enabling you to convert arbitrary objects to Maps using obj as Map expressions:
def obj = someapi.getBlah().getSomeObject()
use (ElegantGroovyFeature) {
def mapOfProperties = obj as Map
}
const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
console.log(map); // Map { foo: "bar", baenter code herez: 42 }
I have JSON looking like:
{
"days": [
{
"mintemp": "21.8"
}
]
}
With Groovy, I parse it like this:
class WeatherRow {
String mintemp
}
def file = new File("data.json")
def slurper = new JsonSlurper().parse(file)
def days = slurper.days
def firstRow = days[0] as WeatherRow
println firstRow.mintemp
But actually, I would like to name my instance variable something like minTemp (or even something completely random, like numberOfPonies). Is there a way in Groovy to map a member of a map passed to a constructor to something else?
To clarify, I was looking for something along the lines of #XmlElement(name="mintemp"), but could not easily find it:
class WeatherRow {
#Element(name="mintemp")
String minTemp
}
Create a constructor that takes a map.
Runnable example:
import groovy.json.JsonSlurper
def testJsonStr = '''
{"days": [
{ "mintemp": "21.8" }
]}'''
class WeatherRow {
String minTemp
WeatherRow(map) {
println "Got called with constructor that takes a map: $map"
minTemp = map.mintemp
}
}
def slurper = new JsonSlurper().parseText(testJsonStr)
def days = slurper.days
def firstRow = days[0] as WeatherRow
println firstRow.minTemp
Result:
Got called with constructor that takes a map: [mintemp:21.8]
21.8
(of course you'd remove the println line, it's just there for the demo)
You can achieve this using annotation and simple custom annotation processor like this:
1. Create a Custom Annotation Class
#Retention(RetentionPolicy.RUNTIME)
#interface JsonDeserializer {
String[] names() default []
}
2. Annotate your instance fields with the custom annotation
class WeatherRow{
#JsonDeserializer(names = ["mintemp"])
String mintemp;
#JsonDeserializer(names = ["mintemp"])
String minTemp;
#JsonDeserializer(names = ["mintemp"])
String numberOfPonies;
}
3. Add custom json deserializer method using annotation processing:
static WeatherRow fromJson(def jsonObject){
WeatherRow weatherRow = new WeatherRow();
try{
weatherRow = new WeatherRow(jsonObject);
}catch(MissingPropertyException ex){
//swallow missing property exception.
}
WeatherRow.class.getDeclaredFields().each{
def jsonDeserializer = it.getDeclaredAnnotations()?.find{it.annotationType() == JsonDeserializer}
def fieldNames = [];
fieldNames << it.name;
if(jsonDeserializer){
fieldNames.addAll(jsonDeserializer.names());
fieldNames.each{i ->
if(jsonObject."$i")//TODO: if field type is not String type custom parsing here.
weatherRow."${it.name}" = jsonObject."$i";
}
}
};
return weatherRow;
}
Example:
def testJsonStr = '''
{
"days": [
{
"mintemp": "21.8"
}
]
}'''
def parsedWeatherRows = new JsonSlurper().parseText(testJsonStr);
assert WeatherRow.fromJson(parsedWeatherRows.days[0]).mintemp == "21.8"
assert WeatherRow.fromJson(parsedWeatherRows.days[0]).minTemp == "21.8"
assert WeatherRow.fromJson(parsedWeatherRows.days[0]).numberOfPonies == "21.8"
Check the full working code at groovyConsole.
take these objects
class Obj1 {
Obj2 obj2
}
class Obj2 {
Obj3 obj3
}
class Obj3 {
String tryme
}
Now, Crud operations on this model is happening by means of an angularjs app. The angular app sends back the fields that changed. so for example, it may send
[
{
"jsonPath": "/obj2/obj3/tryme",
"newValue": "New Name"
}
]
So with groovy, is there an easy way to access that nested field? i could do it with java reflection, but thats a lot of code. If not with pojo's, this is a mongodb, so I suppose i can do it with json slurp if its easier, i just don't know. any advice is appreciated.
So to show the problems with the solutions i have found so far. Take this
Obj1 a = new Obj1()
with the edit object of this
[
{
"jsonPath": "/obj2/obj3/tryme",
"newValue": "New Name"
}
]
Doing the pojo route, finding a null field of obj2 is not an issue. The issue is i have no way of knowing what type it is in order to initialize the field and keep walking the tree.
Please refrain from Groovy is typeless, we don't use def around here, everything needs to be statically typed.
So I am also trying this from the JsonSlurp aspect too, just eliminate the pojo all together. But even that is problematic because it seems I'm back to iterating a map of maps to get to the field. Same problem, easier to solve.
class MongoRecordEditor {
def getProperty(def object, String propertyPath) {
propertyPath.tokenize('/').inject object, {obj, prop ->
def retObj = obj[prop]
if (retObj == null){
println obj[prop].class
}
}
}
void setProperty(def object, String propertyPath, Object value) {
def pathElements = propertyPath.tokenize('/')
def objectField
if (pathElements.size() == 1){
objectField = pathElements[0]
} else {
objectField = pathElements[0..-2].join('/')
}
Object parent = getProperty(object, objectField)
parent[pathElements[-1]] = value
}
}
is the culmination of many ideas. Now getting def retObj = obj[prop] to run is a piece of cake. the problem is, if the field isn't initialized, then retObj is always null, therefore i can't get the type that its supposed to be to initialize it.
and yes I know, once I figure out how to make it work, I will type it.
Maybe something like this?
class Obj1 {
Obj2 obj2
}
class Obj2 {
Obj3 obj3
}
class Obj3 {
String tryme
}
def a = new Obj1(obj2: new Obj2(obj3: new Obj3(tryme:"test")))
for (value in "obj2/obj3/tryme".split("/")) {
a = a?."${value}"
}
println a
You could create trait and then make Obj1 use it:
trait DynamicPath {
def get(String path) {
def target = this
for (value in path.split("/")) {
target = target?."${value}"
}
target
}
}
class Obj1 implements DynamicPath{
Obj2 obj2
}
println a.get("obj2/obj3/tryme");
Not sure if this is what you want... And it relies on the objects having a default constructor, and there may be better ways of doing it...
Those caveats aside, given you have:
import groovy.transform.*
import groovy.json.*
#ToString
class Obj1 {
Obj2 obj2
}
#ToString
class Obj2 {
Obj3 obj3
}
#ToString
class Obj3 {
String tryme
}
def changeRequest = '''[
{
"jsonPath": "/obj2/obj3/tryme",
"newValue": "New Name"
}
]'''
Then, you can define a manipulator like so:
def change(Object o, String path, String value) {
Object current = o
String[] pathElements = path.split('/').drop(1)
pathElements[0..-2].each { f ->
if(current."$f" == null) {
current."$f" = current.class.declaredFields.find { it -> f == it.name }?.type.getConstructor().newInstance()
}
current = current."$f"
}
current."${pathElements[-1]}" = value
o
}
And call it like
def results = new JsonSlurper().parseText(changeRequest).collect {
change(new Obj1(), it.jsonPath, it.newValue)
}
To give you a list containing your one new Obj1 instance:
[Obj1(Obj2(Obj3(New Name)))]