I am trying to read and parse a YAML file with SnakeYAML and turn it into a config POJO for my Java app:
// Groovy pseudo-code
class MyAppConfig {
List<Widget> widgets
String uuid
boolean isActive
// Ex: MyAppConfig cfg = new MyAppConfig('/opt/myapp/config.yaml')
MyAppConfig(String configFileUri) {
this(loadMap(configFileUri))
}
private static HashMap<String,HashMap<String,String>> loadConfig(String configFileUri) {
Yaml yaml = new Yaml();
HashMap<String,HashMap<String,String>> values
try {
File configFile = Paths.get(ClassLoader.getSystemResource(configUri).toURI()).toFile();
values = (HashMap<String,HashMap<String,String>>)yaml.load(new FileInputStream(configFile));
} catch(FileNotFoundException | URISyntaxException ex) {
throw new MyAppException(ex.getMessage(), ex);
}
values
}
MyAppConfig(HashMap<String,HashMap<String,String>> yamlMap) {
super()
// Here I want to extract keys from 'yamlMap' and use their values
// to populate MyAppConfig's properties (widgets, uuid, isActive, etc.).
}
}
Example YAML:
widgets:
- widget1
name: blah
age: 3000
isSilly: true
- widget2
name: blah meh
age: 13939
isSilly: false
uuid: 1938484
isActive: false
Since it appears that SnakeYAML only gives me a HashMap<String,<HashMap<String,String>> to represent my config data, it seems as though we can only have 2 nested mapped properties that SnakeYAML supports (the outer map and in the inner map of type <String,String>)...
But what if widgets contains a list/sequence (say, fizzes) which contained a list of, say, buzzes, which contained yet another list, etc? Is this simply a limitation of SnakeYAML or am I using the API incorrectly?
To extract values out of this map, I need to iterate its keys/values and (seemingly) need to apply my own custom validation. Does SnakeYAML provide any APIs for doing this extraction + validation? For instance, instead of hand-rolling my own code to check to see if uuid is a property defined inside the map, it would be great if I could do something like yaml.extract('uuid'), etc. And then ditto for the subsequent validation of uuid (and any other property).
YAML itself contains a lot of powerful concepts, such as anchors and references. Does SnakeYAML handle these concepts? What if an end user uses them in the config file - how am I supposed to detect/validate/enforce them?!? Does SnakeYAML provide an API for doing this?
Do you mean like this:
#Grab('org.yaml:snakeyaml:1.17')
import org.yaml.snakeyaml.*
import org.yaml.snakeyaml.constructor.*
import groovy.transform.*
String exampleYaml = '''widgets:
| - name: blah
| age: 3000
| silly: true
| - name: blah meh
| age: 13939
| silly: false
|uuid: 1938484
|isActive: false'''.stripMargin()
#ToString(includeNames=true)
class Widget {
String name
Integer age
boolean silly
}
#ToString(includeNames=true)
class MyConfig {
List<Widget> widgets
String uuid
boolean isActive
static MyConfig fromYaml(yaml) {
Constructor c = new Constructor(MyConfig)
TypeDescription t = new TypeDescription(MyConfig)
t.putListPropertyType('widgts', Widget)
c.addTypeDescription(t);
new Yaml(c).load(yaml)
}
}
println MyConfig.fromYaml(exampleYaml)
Obviously, that's a script to run in the Groovy console, you wouldn't need the #Grab line, as you probably already have the library in your classpath ;-)
Related
I'm trying to find a nice way to validate a body using DTO (using the brilliant class-validator and class-transformer libraries). It works really well, even for nested structures but in my case I'd like to have the body property based on some conditions.
Example that will probably help to understand:
Let's imagine my body should always have selectedCategory.
Based on that field, the content could either be from category 1, which contains prop1 OR from category 2, which contains prop2.
I do not want to allow a null for both of them, I really want to have to either have prop1 defined or prop2 based on the selectedCategory.
I think that I could use a pipe, but then how can I specify the correct DTO to use?
I've built a "base" class with all the common properties and few other classes that inherit from it.
I could instantiate the pipe manually based on the property selectedCategory, that'd be ideal but I have no clue what to pass as a second argument of the pipe (metadata).
Thanks for your help.
Have you tried using groups?
Instead of having multiple DTOs, you just create one DTO. Every property is assigned to one or multiple groups:
#Min(12, {groups: ['registration', 'update']})
age: number;
#Length(2, 20, {groups: ['registration']})
name: string;
You can then conditionally pass the groups to class transformer / validator:
#Injectable()
export class ConditionalValidationPipe implements PipeTransform {
async transform(entity: any, metadata: ArgumentMetadata) {
// Dynamically determine the groups
const groups = [];
if (entity.selectedCategory === 1) {
groups.push('registration');
}
// Transform to class with groups
const entityClass = plainToClass(EntityDto, entity, { groups })
// Validate with groups
const errors = await validate(entityClass, { groups });
if (errors.length > 0) {
throw this.createError(errors);
}
return entityClass;
}
}
Have you tried the ValidateIf statement?
You can have multiple validations for props1 or props2 and apply them if selectedCategory is "category 1" or "category 2" accordingly.
I am new to functional programming paradigm and hoping to learn the concepts using groovy. I have a json text containing a list of several person objects like the following:
{
"persons":[
{
"id":1234,
"lastname":"Smith",
"firstname":"John"
},
{
"id":1235,
"lastname":"Lee",
"firstname":"Tommy"
}
]
}
What I am trying to do store them in list or array of Person groovy class like the following:
class Person {
def id
String lastname
String firstname
}
I would like to do this using a closure. I tried something like:
def personsListJson= new JsonSlurper().parseText(personJsonText) //personJsonText is raw json string
persons = personsListJson.collect{
new Person(
id:it.id, firstname:it.firstname, lastname:it.lastname)
}
This didn't work. Does collect operations supposed to behave this way? If so then how do I write it?
Try
personsListJson.persons.collect {
new Person( id:it.id, firstname:it.firstname, lastname:it.lastname )
}
And as there is a 1:1 mapping between the json and the constructor parameters, you can simplify that to:
personsListJson.persons.collect {
new Person( it )
}
But I'd keep the first method, as if the Json got an extra value in it (maybe out of your control) then the second method would break
You can try it-
List<JSON> personsListJson = JSON.parse(personJsonText);
persons = personsListJson.collect{
new Person(id:it.id, firstname:it.firstname, lastname:it.lastname)
}
I want to implement an 'active' flag for rules in my DSL. Here's how I wanted it to look like:
Shipping("Standard") {
active: true
description: "some text"
rules {
... define rules here
}
}
Here's how I got everything running following several tutorials:
Script dslScript = new GroovyShell().parse(new File("Standard"))
dslScript.metaClass.Shipping = { String name, Closure cl ->
ShippingDelegate delegate = new ShippingDelegate()
delegate.name = name
cl.delegate = delegate
cl.setResolveStrategy Closure.DELEGATE_FIRST
cl()
}
dslScript.run()
ShippingDelegate is simple:
class ShippingDelegate {
String name
void rules(Closure cl) {
... do stuff here
}
}
It all runs fine without complaints but how can I access 'active' or 'description'?
What does this syntax actually do anyway? It looks like a map assignment, but there is none. Or does the groovy compiler treat it as an incomplete ternary operator?
May I suggest a small change in your DSL so that your design can be simplified?
Edited, it is not clear in you example if you have more than one shipping instance. In my second try, I assume that the answer is yes
class ShippingRules {
boolean active
String description
String name
ShippingRules(String name) {
this.name=name
}
def rules(Closure c) {
c.delegate=this
c()
}
}
abstract class ShippingRulesScript extends Script {
def shipppingRules =[]
def shipping(String name, Closure c) {
def newRules=new ShippingRules(name)
shipppingRules << newRules
c.delegate=newRules
c()
}
}
def cfg= new CompilerConfiguration(
scriptBaseClass:ShippingRulesScript.name
)
Script dslScript = new GroovyShell(cfg).parse(new File("Standard"))
dslScript.run()
The DSL should be changed to this:
shipping("Standard") {
active= true
description= "some text"
rules {
... define rules here
}
}
shipping("International") {
active= true
description= "some text"
rules {
... define rules here
}
}
I.e. lose the capital to shipping, and use assignments instead of colons.
You would then later be able to retrieve the shipping rules from your dslScript shippingRules variable.
disclaimer: I cannot test my code right now, so there may be some typos in the code, but you get the general idea: use a base class where you provide your rules and properties to your script.
I asked a similar question on Google+, see here.
The summary is: you can use the map syntax only on constructors (ctors) and as function parameters.
Interesting is that it doesn't throw an exception.
I'm wondering what is the best way to retrieve nested properties in Groovy, taking a given Object and arbitrary "property" String. I would like to something like this:
someGroovyObject.getProperty("property1.property2")
I've had a hard time finding an example of others wanting to do this, so maybe I'm not understanding some basic Groovy concept. It seems like there must be some elegant way to do this.
As reference, there is a feature in Wicket that is exactly what I'm looking for, called the PropertyResolver:
http://wicket.apache.org/apidocs/1.4/org/apache/wicket/util/lang/PropertyResolver.html
Any hints would be appreciated!
I don't know if Groovy has a built-in way to do this, but here are 2 solutions. Run this code in the Groovy Console to test it.
def getProperty(object, String property) {
property.tokenize('.').inject object, {obj, prop ->
obj[prop]
}
}
// Define some classes to use in the test
class Name {
String first
String second
}
class Person {
Name name
}
// Create an object to use in the test
Person person = new Person(name: new Name(first: 'Joe', second: 'Bloggs'))
// Run the test
assert 'Joe' == getProperty(person, 'name.first')
/////////////////////////////////////////
// Alternative Implementation
/////////////////////////////////////////
def evalProperty(object, String property) {
Eval.x(object, 'x.' + property)
}
// Test the alternative implementation
assert 'Bloggs' == evalProperty(person, 'name.second')
Groovy Beans let you access fields directly. You do not have to define getter/setter methods. They get generated for you. Whenever you access a bean property the getter/setter method is called internally. You can bypass this behavior by using the .# operator. See the following example:
class Person {
String name
Address address
List<Account> accounts = []
}
class Address {
String street
Integer zip
}
class Account {
String bankName
Long balance
}
def person = new Person(name: 'Richardson Heights', address: new Address(street: 'Baker Street', zip: 22222))
person.accounts << new Account(bankName: 'BOA', balance: 450)
person.accounts << new Account(bankName: 'CitiBank', balance: 300)
If you are not dealing with collections you can simply just call the field you want to access.
assert 'Richardson Heights' == person.name
assert 'Baker Street' == person.address.street
assert 22222 == person.address.zip
If you want to access a field within a collection you have to select the element:
assert 'BOA' == person.accounts[0].bankName
assert 300 == person.accounts[1].balance
You can also use propertyMissing. This is what you might call Groovy's built-in method.
Declare this in your class:
def propertyMissing(String name) {
if (name.contains(".")) {
def (String propertyname, String subproperty) = name.tokenize(".")
if (this.hasProperty(propertyname) && this."$propertyname".hasProperty(subproperty)) {
return this."$propertyname"."$subproperty"
}
}
}
Then refer to your properties as desired:
def properties = "property1.property2"
assert someGroovyObject."$properties" == someValue
This is automatically recursive, and you don't have to explicitly call a method. This is only a getter, but you can define a second version with parameters to make a setter as well.
The downside is that, as far as I can tell, you can only define one version of propertyMissing, so you have to decide if dynamic path navigation is what you want to use it for.
See
https://stackoverflow.com/a/15632027/2015517
It uses ${} syntax that can be used as part of GString
I've built a custom builder in Groovy by extending BuilderSupport. It works well when configured like nearly every builder code sample out there:
def builder = new MyBuilder()
builder.foo {
"Some Entry" (property1:value1, property2: value2)
}
This, of course, works perfectly. The problem is that I don't want the information I'm building to be in the code. I want to have this information in a file somewhere that is read in and built into objects by the builder. I cannot figure out how to do this.
I can't even make this work by moving the simple entry around in the code.
This works:
def textClosure = { "Some Entry" (property1:value1, property2: value2) }
builder.foo(textClosure)
because textClosure is a closure.
If I do this:
def text = '"Some Entry" (property1:value1, property2: value2)'
def textClosure = { text }
builder.foo(textClosure)
the builder only gets called for the "foo" node. I've tried many variants of this, including passing the text block directly into the builder without wrapping it in a closure. They all yield the same result.
Is there some way I take a piece of arbitrary text and pass it into my builder so that it will be able to correctly parse and build it?
Your problem is that a String is not Groovy code. The way ConfigSlurper handles this is to compile the text into an instance of Script using GroovyClassLoader#parseClass. e.g.,
// create a Binding subclass that delegates to the builder
class MyBinding extends Binding {
def builder
Object getVariable(String name) {
return { Object... args -> builder.invokeMethod(name,args) }
}
}
// parse the script and run it against the builder
new File("foo.groovy").withInputStream { input ->
Script s = new GroovyClassLoader().parseClass(input).newInstance()
s.binding = new MyBinding(builder:builder)
s.run()
}
The subclass of Binding simply returns a closure for all variables that delegates the call to the builder. So assuming foo.groovy contains:
foo {
"Some Entry" (property1:value1, property2: value2)
}
It would be equivalent to your code above.
I think the problem you described is better solved with a slurper or parser.
See:
http://groovy.codehaus.org/Reading+XML+using+Groovy%27s+XmlSlurper
http://groovy.codehaus.org/Reading+XML+using+Groovy%27s+XmlParser
for XML based examples.
In your case. Given the XML file:
<foo>
<entry name='Some Entry' property1="value1" property2="value2"/>
</foo>
You could slurp it with:
def text = new File("test.xml").text
def foo = new XmlSlurper().parseText(text)
def allEntries = foo.entry
allEntries.each {
println it.#name
println it.#property1
println it.#property2
}
Originally, I wanted to be able to specify
"Some Entry" (property1:value1, property2: value2)
in an external file. I'm specifically trying to avoid XML and XML-like syntax to make these files easier for regular users to create and modify. My current solution uses ConfigSlurper and the file now looks like:
"Some Entry"
{
property1 = value1
property2 = value2
}
ConfigSlurper gives me a map like this:
["Some Entry":[property1:value1,property2:value2]]
It's pretty simple to use these values to create my objects, especially since I can just pass the property/value map into the constructor.