List as map value in grails - list

I need to populate a Map so that:
The Key is a String
The Value is a List of Strings
The process is to go through all the records in a table that has two text fields : "parameter" and "value". "Parameter" is not unique an has many duplicates. So what I intent to do is:
def all = MyTable.findAll()
def mymap = [:]
all.each {
// add to mymap the element "it.value" to the list that has "it.parameter" as key
}
Any clues ?
Thanks

There is a IMHO little bit simpler way doing this by using 'withDefault' introduced in Groovy 1.7:
all = [
[parameter: 'foo', value: 'aaa'],
[parameter: 'foo', value: 'bbb'],
[parameter: 'bar', value: 'ccc'],
[parameter: 'baz', value: 'ddd']
]
def myMap = [:].withDefault { [] }
all.each {
myMap[it.parameter] << it.value
}
assert myMap.size() == 3
assert myMap.foo == ['aaa','bbb']
assert myMap.bar == ['ccc']
assert myMap.baz == ['ddd']

You can use the Map.groupBy method, which will split the collection into a map of groups based on the passed in closure. Here's a full example, which also calls collect to make each parameter point to just the values:
all = [
[parameter: 'foo', value: 'aaa'],
[parameter: 'foo', value: 'bbb'],
[parameter: 'bar', value: 'ccc'],
[parameter: 'baz', value: 'ddd']
]
tmpMap = all.groupBy{it.parameter}
myMap = [:].putAll(tmpMap.collect{k, v -> [k, v.value] as MapEntry})
assert myMap == [foo: ['aaa', 'bbb'], bar: ['ccc'], baz:['ddd']]

Related

Obtain values that match from several conditions on a list in javascript

I'm trying to obtain a list from a list in Javascript.
This is the list:
const cars = [
{
id: 1,
brand: "Mercedes Benz",
properties: [
{
property: "Mechanical",
value: 2,
},
{
property: "Chemical",
value: 2,
},
{
property: "Pressure",
value: 3,
}],
},
{
id: 2,
brand: "BMW",
properties: [
{
property: "Mechanical",
value: 5,
},
{
property: "Chemical",
value: 3,
},
{
property: "Pressure",
value: 6,
}],
}
]
I need the cars which match some properties property with a value greater than X, Y
For example, I want the cars which Mechanical properties have a value greater than 3 and a Pressure greater than 4. In that case I'll obtain the complete object with id 2.
Does anyone have an idea? That is having me a hard time
Tip: I paste it on a Node REPL ;)
This is what I tried but I obtain nothing:
cars.filter(car => car.properties.some((p1, p2) => {return ((p1.property === "Mechanical" && p1.value > 3) && (p2.property === "Pressure" && p2.value > 4))}))
Thanks in advance
You need to iterate all items and check each one for it's relevant condition, and if all items pass, return true. In your case you are checking each item for all conditions, and since no item's property can have both "Mechanical" and "Pressure" values at the same time, all fail.
When an array needs to pass all conditions, you should use Array.every() that will only return true, if all iterated items would return true.
To make this more generic, we can store the conditions as functions in an object or a Map. If there is a condition function for this property, we'll use the function to check the value. If not, we can return true immediately.
Note: this answer uses Optional chaining (?.) and the Nullish coalescing operator (??) to return true if the predicate doesn't exist. If your running environment doesn't support this operators replace the line with predicate[property] ? predicate[property](value) : true (see 2nd example).
const fn = (predicate, cars) =>
cars.filter(car => car.properties.every(({ property, value }) =>
predicate[property]?.(value) ?? true
))
const cars = [{"id":1,"brand":"Mercedes Benz","properties":[{"property":"Mechanical","value":2},{"property":"Chemical","value":2},{"property":"Pressure","value":3}]},{"id":2,"brand":"BMW","properties":[{"property":"Mechanical","value":5},{"property":"Chemical","value":3},{"property":"Pressure","value":6}]}]
const predicate = {
Mechanical: value => value > 3,
Pressure: value => value > 4,
}
const result = fn(predicate, cars)
console.log(result)
Or using a ternary:
const fn = (predicate, cars) =>
cars.filter(car => car.properties.every(({ property, value }) =>
predicate[property] ? predicate[property](value) : true
))
const cars = [{"id":1,"brand":"Mercedes Benz","properties":[{"property":"Mechanical","value":2},{"property":"Chemical","value":2},{"property":"Pressure","value":3}]},{"id":2,"brand":"BMW","properties":[{"property":"Mechanical","value":5},{"property":"Chemical","value":3},{"property":"Pressure","value":6}]}]
const predicate = {
Mechanical: value => value > 3,
Pressure: value => value > 4,
}
const result = fn(predicate, cars)
console.log(result)

Query Django JSONFields that are a list of dictionaries

Given a Django JSONField that is structured as a list of dictionaries:
# JSONField "materials" on MyModel:
[
{"some_id": 123, "someprop": "foo"},
{"some_id": 456, "someprop": "bar"},
{"some_id": 789, "someprop": "baz"},
]
and given a list of values to look for:
myids = [123, 789]
I want to query for all MyModel instances that have a matching some_id anywhere in those lists of dictionaries. I can do this to search in dictionaries one at a time:
# Search inside the third dictionary in each list:
MyModel.objects.filter(materials__2__some_id__in=myids)
But I can't seem to construct a query to search in all dictionaries at once. Is this possible?
Given the clue here from Davit Tovmasyan to do this by incrementing through the match_targets and building up a set of Q queries, I wrote this function that takes a field name to search, a property name to search against, and a list of target matches. It returns a new list containing the matching dictionaries and the source objects they come from.
from iris.apps.claims.models import Claim
from django.db.models import Q
def json_list_search(
json_field_name: str,
property_name: str,
match_targets: list
) -> list:
"""
Args:
json_field_name: Name of the JSONField to search in
property_name: Name of the dictionary key to search against
match_targets: List of possible values that should constitute a match
Returns:
List of dictionaries: [
{"claim_id": 123, "json_obj": {"foo": "y"},
{"claim_id": 456, "json_obj": {"foo": "z"}
]
Example:
results = json_list_search(
json_field_name="materials_data",
property_name="material_id",
match_targets=[1, 22]
)
# (results truncated):
[
{
"claim_id": 1,
"json_obj": {
"category": "category_kmimsg",
"material_id": 1,
},
},
{
"claim_id": 2,
"json_obj": {
"category": "category_kmimsg",
"material_id": 23,
}
},
]
"""
q_keys = Q()
for match_target in match_targets:
kwargs = {
f"{json_field_name}__contains": [{property_name: match_target}]
}
q_keys |= Q(**kwargs)
claims = Claim.objects.filter(q_keys)
# Now we know which ORM objects contain references to any of the match_targets
# in any of their dictionaries. Extract *relevant* objects and return them
# with references to the source claim.
results = []
for claim in claims:
data = getattr(claim, json_field_name)
for datum in data:
if datum.get(property_name) and datum.get(property_name) in match_targets:
results.append({"claim_id": claim.id, "json_obj": datum})
return results
contains might help you. Should be something like this:
q_keys = Q()
for _id in myids:
q_keys |= Q(materials__contains={'some_id': _id})
MyModel.objects.filter(q_keys)

AWS RDS Data API executeStatement not return column names

I'm playing with the New Data API for Amazon Aurora Serverless
Is it possible to get the table column names in the response?
If for example I run the following query in a user table with the columns id, first_name, last_name, email, phone:
const sqlStatement = `
SELECT *
FROM user
WHERE id = :id
`;
const params = {
secretArn: <mySecretArn>,
resourceArn: <myResourceArn>,
database: <myDatabase>,
sql: sqlStatement,
parameters: [
{
name: "id",
value: {
"stringValue": 1
}
}
]
};
let res = await this.RDS.executeStatement(params)
console.log(res);
I'm getting a response like this one, So I need to guess which column corresponds with each value:
{
"numberOfRecordsUpdated": 0,
"records": [
[
{
"longValue": 1
},
{
"stringValue": "Nicolas"
},
{
"stringValue": "Perez"
},
{
"stringValue": "example#example.com"
},
{
"isNull": true
}
]
]
}
I would like to have a response like this one:
{
id: 1,
first_name: "Nicolas",
last_name: "Perez",
email: "example#example.com",
phone: null
}
update1
I have found an npm module that wrap Aurora Serverless Data API and simplify the development
We decided to take the current approach because we were trying to cut down on the response size and including column information with each record was redundant.
You can explicitly choose to include column metadata in the result. See the parameter: "includeResultMetadata".
https://docs.aws.amazon.com/rdsdataservice/latest/APIReference/API_ExecuteStatement.html#API_ExecuteStatement_RequestSyntax
Agree with the consensus here that there should be an out of the box way to do this from the data service API. Because there is not, here's a JavaScript function that will parse the response.
const parseDataServiceResponse = res => {
let columns = res.columnMetadata.map(c => c.name);
let data = res.records.map(r => {
let obj = {};
r.map((v, i) => {
obj[columns[i]] = Object.values(v)[0]
});
return obj
})
return data
}
I understand the pain but it looks like this is reasonable based on the fact that select statement can join multiple tables and duplicated column names may exist.
Similar to the answer above from #C.Slack but I used a combination of map and reduce to parse response from Aurora Postgres.
// declarative column names in array
const columns = ['a.id', 'u.id', 'u.username', 'g.id', 'g.name'];
// execute sql statement
const params = {
database: AWS_PROVIDER_STAGE,
resourceArn: AWS_DATABASE_CLUSTER,
secretArn: AWS_SECRET_STORE_ARN,
// includeResultMetadata: true,
sql: `
SELECT ${columns.join()} FROM accounts a
FULL OUTER JOIN users u ON u.id = a.user_id
FULL OUTER JOIN groups g ON g.id = a.group_id
WHERE u.username=:username;
`,
parameters: [
{
name: 'username',
value: {
stringValue: 'rick.cha',
},
},
],
};
const rds = new AWS.RDSDataService();
const response = await rds.executeStatement(params).promise();
// parse response into json array
const data = response.records.map((record) => {
return record.reduce((prev, val, index) => {
return { ...prev, [columns[index]]: Object.values(val)[0] };
}, {});
});
Hope this code snippet helps someone.
And here is the response
[
{
'a.id': '8bfc547c-3c42-4203-aa2a-d0ee35996e60',
'u.id': '01129aaf-736a-4e86-93a9-0ab3e08b3d11',
'u.username': 'rick.cha',
'g.id': 'ff6ebd78-a1cf-452c-91e0-ed5d0aaaa624',
'g.name': 'valentree',
},
{
'a.id': '983f2919-1b52-4544-9f58-c3de61925647',
'u.id': '01129aaf-736a-4e86-93a9-0ab3e08b3d11',
'u.username': 'rick.cha',
'g.id': '2f1858b4-1468-447f-ba94-330de76de5d1',
'g.name': 'ensightful',
},
]
Similar to the other answers, but if you are using Python/Boto3:
def parse_data_service_response(res):
columns = [column['name'] for column in res['columnMetadata']]
parsed_records = []
for record in res['records']:
parsed_record = {}
for i, cell in enumerate(record):
key = columns[i]
value = list(cell.values())[0]
parsed_record[key] = value
parsed_records.append(parsed_record)
return parsed_records
I've added to the great answer already provided by C. Slack to deal with AWS handling empty nullable character fields by giving the response { "isNull": true } in the JSON.
Here's my function to handle this by returning an empty string value - this is what I would expect anyway.
const parseRDSdata = (input) => {
let columns = input.columnMetadata.map(c => { return { name: c.name, typeName: c.typeName}; });
let parsedData = input.records.map(row => {
let response = {};
row.map((v, i) => {
//test the typeName in the column metadata, and also the keyName in the values - we need to cater for a return value of { "isNull": true } - pflangan
if ((columns[i].typeName == 'VARCHAR' || columns[i].typeName == 'CHAR') && Object.keys(v)[0] == 'isNull' && Object.values(v)[0] == true)
response[columns[i].name] = '';
else
response[columns[i].name] = Object.values(v)[0];
}
);
return response;
}
);
return parsedData;
}

Build dynamic closure list from a list in order to pass it to OrderBy in Groovy

I am following this article to set the Order for sorting.
Excerpt from above article
class Language {
String name
boolean dynamic
String toString() { "name: $name, dynamic: $dynamic" }
}
def languages = [
new Language(name: 'Groovy', dynamic: true),
new Language(name: 'Java', dynamic: false),
new Language(name: 'Clojure', dynamic: true)
]
def list = ['name', 'dynamic']
sh = new GroovyShell()
closure = sh.evaluate("{ }")
def cList = list.collect { closure(it) }
println cList
// We order first on dynamic property and then name property.
def orderByDynamicAndName = new OrderBy([{ it.dynamic }, { it.name }])
Here, I would like to pass the list of closures dynamically from a list.
Say, there is a list and list may vary in element size in different applications / classes. This is main reason I wanted the dynamic closure list.
def list = ['name', 'dynamic']
From the above list, want to generate it as list of closure and pass it to OrderBy class as argument.
//Build closure list. But, not sure how to generate it from above list
def cList =
def orderByDynamicAndName = new OrderBy(cList)
Tried to refer this thread, but some how could not generate cList as desired
Tried to build cList as shown below; getting errors
def list = ['name', 'dynamic']
sh = new GroovyShell()
closure = sh.evaluate("{ fieldName -> \"it\".fieldName }")
def cList = list.collect { closure(it) }
Error:
Exception thrown
groovy.lang.MissingPropertyException: No such property: fieldName for class: java.lang.String
How to over come this?
class Language {
String name
boolean dynamic
String toString() { "name: $name, dynamic: $dynamic" }
}
def languages = [
new Language(name: 'Groovy', dynamic: true),
new Language(name: 'Java', dynamic: false),
new Language(name: 'Clojure', dynamic: true)
]
def list = ['dynamic', 'name']
def cList = list.collect{ propName-> { target-> target[propName] } }
def orderBy = new OrderBy(cList)
def sortedLanguages = languages.toSorted(orderBy)
println languages
println sortedLanguages
actually this expression
list.collect{ propName-> { target-> target[propName] } }
converts list of property names to list of closures
['dynamic', 'name'] => [ { target-> target['dynamic'] }, { target-> target['name'] } ]
and target is just a parameter name in the closure.
later, when we call sort, each closure { target-> target[propName] } will be called against an object in a sorting array and our closure returns the value by a property name.
After trial and error, below code worked for me in order to create the list of closures.
def list = ['dynamic', 'name']
def tempClosureString = list.collect { element -> "{it.$element}" }.join(',')
def cList = new GroovyShell().evaluate("[ $tempClosureString ]")
def orderByDynamicAndName = new OrderBy(cList)
I welcome if there are better alternatives.

groovy: create a list of values with all strings

I am trying to iterate through a map and create a new map value. The below is the input
def map = [[name: 'hello', email: ['on', 'off'] ], [ name: 'bye', email: ['abc', 'xyz']]]
I want the resulting data to be like:
[hello: ['on', 'off'], bye: ['abc', 'xyz']]
The code I have right now -
result = [:]
map.each { key ->
result[random] = key.email.each {random ->
"$random"
}
}
return result
The above code returns
[hello: [on, off], bye: [abc, xyz]]
As you can see from above, the quotes from on, off and abc, xyz have disappeared, which is causing problems for me when i am trying to do checks on the list value [on, off]
It should not matter. If you see the result in Groovy console, they are still String.
Below should be sufficient:
map.collectEntries {
[ it.name, it.email ]
}
If you still need the single quotes to create a GString instead of a String, then below tweak would be required:
map.collectEntries {
[ it.name, it.email.collect { "'$it'" } ]
}
I personally do not see any reasoning behind doing the later way. BTW, map is not a Map, it is a List, you can rename it to avoid unnecessary confusions.
You could convert it to a json object and then everything will have quotes
This does it. There should/may be a groovier way though.
def listOfMaps = [[name: 'hello', email: ['on', 'off'] ], [ name: 'bye', email: ['abc', 'xyz']]]
def result = [:]
listOfMaps.each { map ->
def list = map.collect { k, v ->
v
}
result[list[0]] = ["'${list[1][0]}'", "'${list[1][1]}'"]
}
println result