Given this map
r = {
'items': [
{
'id': '1',
'name': 'foo'
},
{
'id': '2',
'name': 'bar'
}
]
}
I am trying to get the 'id' for 'name'=='foo'. I have this:
Id = [api['id'] for api in r['items'] if 'foo' in api['name']]
But then Id == ['1']. I want it to = "1". I can do this:
Id = [api['id'] for api in r['items'] if 'foo' in api['name']][0]
But that seems like a workaround. Is there a way to write that in such a way as to pass only the value of api['id'] rather than the value within a list?
You can use a generator:
Id = next(api['id'] for api in r['items'] if api['name'] == 'foo')
The added benefit is that the iteration will be stopped as soon as you encounter matching object, whereas your original code would process all of the original list and create a new one, only to extract its first element.
Related
I'm trying to fetch the value of element based on it's value in the complex JSON file.
Trying to fetch the value attribute (Which is 100) if the currency ='BRL' and the index will be subject to change so I just want to try with condition based.
I just tried below so far:
Script:
function test()
{
var result = jsonpath.query(payload,"$..client_balance[?(#.type == 'AVAILABLE')]");
console.log(result);
}
test();
Output:
[
{
amount: { currency: 'BRL', value: '100', skip: false },
type: 'AVAILABLE'
},
{
amount: { currency: 'USD', value: '10', skip: false },
type: 'AVAILABLE'
}
]
Now, I just wanna fetch the value attribute (Which is 100) if the currency code = 'BRL'. I tried to apply the [?(#.currency == 'BRL')]
in the tail of the path variable but it returned empty array.
can someone help me to solve this problem.
Updated:
Tried filter function to get the specific element value.
console.log(Object.values(payload).filter(element =>{
element.currency === 'BRL';
}));
Output:
[]
console.log(Object.values(payload).filter(element =>{
return element.amount.currency === 'BRL';
}));
I think this should work
This is a bit of a complex query, but it should get you what you're looking for.
Start with what you have, which returns the result set you posted:
$..client_balance[?(#.type == 'AVAILABLE')]
Add to this another filter which looks inside the amount field at the currency field for the comparison:
$..client_balance[?(#.type == 'AVAILABLE')][?(#.amount.currency === 'BRL')]
This should give just the one element:
[
{
amount: { currency: 'BRL', value: '100', skip: false },
type: 'AVAILABLE'
}
]
From here you want to get the value field, but to get there, you need the path to it, meaning you have to go through the amount and currency fields first.
$..client_balance[?(#.type == 'AVAILABLE')][?(#.amount.currency === 'BRL')].amount.currency.value
This should return
[
100
]
Please note that we are working on a specification for JSON Path. If this library chooses to adhere to it once published, the === will need to change to a == as this is what we've decided to support.
I wrote a program for the following:
Given a list of users, write a function, names_and_roles that returns all of user's names and roles in a string with each value labeled.
I didn't meet the question requirements for the program. I'm guessing it's because I returned a list of dictionaries instead. How could I return a string such that my output looks the same?
users = [
{
'name': 'Homer',
'role': 'Clerk',
'dob': '12/02/1988',
'admin': False
},
{
'name': 'Lisa',
'role': 'Staff',
'dob': '01/30/1965',
'admin': False
},
{
'name': 'Marge',
'role': 'Associate',
'dob': '09/10/1980',
'admin': True
}
]
def names_and_roles(some_users):
employees = {some_user['name']:some_user['role'] for some_user in users}
return employees
employee_list = (names_and_roles(users))
for user, role in employee_list.items():
print("Name:", user)
print("Role:", role)
print("\n")
There are many ways to do that. You should probably try something before posting your question, but here is on way for example:
["Name: {}, Role: {}".format(u['name'], u['role']) for u in users]
Or if it needs to be one big string:
sep = "\n"
sep.join("Name: {}, Role: {}".format(u['name'], u['role']) for u in users)
You could take the code that prints out what is in employee_list and move it into names_and_roles, but adding each line to a string instead of printing it.
Then you should see that it would actually be easier to do that with the original list of users, and avoid making the dictionary.
I have an object array in a reducer that looks like this:
[
{id:1, name:Mark, email:mark#email.com},
{id:2, name:Paul, email:paul#gmail.com},
{id:3,name:sally, email:sally#email.com}
]
Below is my reducer. So far, I can add a new object to the currentPeople reducer via the following:
const INITIAL_STATE = { currentPeople:[]};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case ADD_PERSON:
return {...state, currentPeople: [ ...state.currentPeople, action.payload]};
}
return state;
}
But here is where I'm stuck. Can I UPDATE a person via the reducer using lodash?
If I sent an action payload that looked like this:
{id:1, name:Eric, email:Eric#email.com}
Would I be able to replace the object with the id of 1 with the new fields?
Yes you can absolutely update an object in an array like you want to. And you don't need to change your data structure if you don't want to. You could add a case like this to your reducer:
case UPDATE_PERSON:
return {
...state,
currentPeople: state.currentPeople.map(person => {
if (person.id === action.payload.id) {
return action.payload;
}
return person;
}),
};
This can be be shortened as well, using implicit returns and a ternary:
case UPDATE_PERSON:
return {
...state,
currentPeople: state.currentPeople.map(person => (person.id === action.payload.id) ? action.payload : person),
};
Mihir's idea about mapping your data to an object with normalizr is certainly a possibility and technically it'd be faster to update the user with the reference instead of doing the loop (after initial mapping was done). But if you want to keep your data structure, this approach will work.
Also, mapping like this is just one of many ways to update the object, and requires browser support for Array.prototype.map(). You could use lodash indexOf() to find the index of the user you want (this is nice because it breaks the loop when it succeeds instead of just continuing as the .map would do), once you have the index you could overwrite the object directly using it's index. Make sure you don't mutate the redux state though, you'll need to be working on a clone if you want to assign like this: clonedArray[foundIndex] = action.payload;.
This is a good candidate for data normalization. You can effectively replace your data with the new one, if you normalize the data before storing it in your state tree.
This example is straight from Normalizr.
[{
id: 1,
title: 'Some Article',
author: {
id: 1,
name: 'Dan'
}
}, {
id: 2,
title: 'Other Article',
author: {
id: 1,
name: 'Dan'
}
}]
Can be normalized this way-
{
result: [1, 2],
entities: {
articles: {
1: {
id: 1,
title: 'Some Article',
author: 1
},
2: {
id: 2,
title: 'Other Article',
author: 1
}
},
users: {
1: {
id: 1,
name: 'Dan'
}
}
}
}
What's the advantage of normalization?
You get to extract the exact part of your state tree that you want.
For instance- You have an array of objects containing information about the articles. If you want to select a particular object from that array, you'll have to iterate through entire array. Worst case is that the desired object is not present in the array. To overcome this, we normalize the data.
To normalize the data, store the unique identifiers of each object in a separate array. Let's call that array as results.
result: [1, 2, 3 ..]
And transform the array of objects into an object with keys as the id(See the second snippet). Call that object as entities.
Ultimately, to access the object with id 1, simply do this- entities.articles["1"].
If you want to replace the old data with new data, you can do this-
entities.articles["1"] = newObj;
Use native splice method of array:
/*Find item index using lodash*/
var index = _.indexOf(currentPeople, _.find(currentPeople, {id: 1}));
/*Replace item at index using splice*/
arr.splice(index, 1, {id:1, name:'Mark', email:'mark#email.com'});
I have a DynamoDB table which contains objects looking like the following:
{
'username': ...,
'subscriptions': [
...
],
...
}
And I would like to filter subscriptions for each user based on some criteria, and get back the objects which have subscriptions matching the criteria, but filter those objects so that only the matching subscriptions are present.
If a user has subscribed to 50 things, but only 3 of them match, I would like to get back an object where the `subscriptions' field is only 3 elements long. I also need the other information contained in the object.
To give a more specific example, suppose that I have two elements in my table:
{
'username': 'alice',
'subscriptions': [
1,
2
],
'email': 'alice#a.com'
},
{
'username': 'bob',
'subscriptions': [
2,
3
],
'email': 'bob#a.com'
}
And I would like to filter to get the subscription `1'. I would like to get back
{
'username': 'alice',
'subscriptions': [
1
],
'email': 'alice#a.com'
}
Or perhaps some other structure which contains all the necessary information (and no other information in order to save bandwidth).
I believe I can do this with scan(), but I do not know the specifics.
How does one remove subitems from a document. So say I have a document called sales with each sale has a sale.item which contains {name,price,code}.
I want to remove each item which is not valid, by checking the code for blank or null.
Trying something like below fails with errors, am not sure if I need to use sub-query and how.
FOR sale in sales
FOR item in sale.items
FILTER item.code == ""
REMOVE item IN sale.items
Another attempt
FOR sale in sales
LET invalid = (
FOR item in sale.items
FILTER item.code == ""
RETURN item
)
REMOVE invalid IN sale.items LET removed = OLD RETURN removed
The following query will rebuild the items for each document in sales. It will only keep item whose code is not null and not the empty string:
FOR doc IN sales
LET newItems = (
FOR item IN doc.items
FILTER item.code != null && item.code != ''
RETURN item
)
UPDATE doc WITH { items: newItems } IN sales
Here is the test data I used:
db.sales.insert({
items: [
{ code: null, what: "delete-me" },
{ code: "", what: "delete-me-too" },
{ code: "123", what: "better-keep-me" },
{ code: true, what: "keep-me-too" }
]
});
db.sales.insert({
items: [
{ code: "", what: "i-will-vanish" },
{ code: null, what: "i-will-go-away" },
{ code: "abc", what: "not me!" }
]
});
db.sales.insert({
items: [
{ code: "444", what: "i-will-remain" },
{ code: null, what: "i-will-not" }
]
});
There's a better way to do this, without sub-queries. Instead, a function for removing an array element, will be used:
FOR doc IN sales
FOR item IN doc.items
FILTER item.code == ""
UPDATE doc WITH { items: REMOVE_VALUE( doc.items, item ) } IN sales
REMOVE_VALUE takes an array as the first argument, and an array item inside that array as the second argument, and returns an array that has all the items of the first argument, but without that specific item that was in the second argument.
Example:
REMOVE_VALUE([1, 2, 3], 3) = [1, 2]
Example with subdocuments being the values:
REMOVE_VALUE( [ {name: cake}, {name: pie, taste: delicious}, {name: cheese} ] , {name: cheese}) = [ {name: cake}, {name: pie, taste: delicious} ]
You cannot just use REMOVE_VALUE separately, the way you use the REMOVE command separately. You must use it as part of an UPDATE command not as part of a REMOVE command. Unfortunately, the way it works is to make a copy of the "items" list inside your one specific "doc" that you are currently dealing with, but the copy has the subdocument you don't like, removed from the "items" list. That new copy of the list replaces the old copy of the items list.
There is one more, most efficient way to remove subdocuments from a list - and that is by accessing the specific cell of the list with items[2] - and you have to use fancy array functions even fancier than the one I used here, to find out the specific cell in the list (whether it's [2] or [3] or [567]) and then to replace the contents of that cell with Null, using the UPDATE command, and then to set the options to KeepNull = false. That's the "most efficient" way to do it but it would be a monstrous looking complicated query ): I might write that query later and put it here but right now .. I would honestly suggest using the method I described above, unless you have a thousand subdocuments in each list.