I have a sample object structure like below
Even though there are three types of addresses (address, employeeAddress, shippingAddress), they all represent the same data structure called address. From this object structure, I need to get all the addresses from the above structure.The object structure might be defined in using a JSON Schema format.
Also the addresses need not be always as part of the same hierarchy. For example in the above, shippingAddress and employeeAddress are at different hierarchy.
I tried with object's hasOwnProperty, but did not work the way as expected. Did not get much help from the filter method in lodash also. Is there an elegant way to achieve this?
{
"user": {
"firstName": "John",
"lastName": "Steve",
"address": {
"houseNo": "24",
"city": "CA",
"country": {
"code": "US",
"name": "United States"
}
}
},
"employee": {
"employeeID": "443434",
"employeeName": "Steve",
"employeeAddress": {
"houseNo": "244",
"city": "NJ",
"country": {
"code": "US",
"name": "United States"
}
}
},
"assistant": {
"assitantID": "443434",
"employeeName": "Steve",
"shippingDetails": {
"shippingAddress": {
"houseNo": "2444",
"city": "LA",
"country": {
"code": "US",
"name": "United States"
}
}
}
}
}
You could use recursion for this and create a function that takes input data and schema object. Then on each level another function checks if the current object matches schema structure.
const data = {"user":{"firstName":"John","lastName":"Steve","address":{"houseNo":"24","city":"CA","country":{"code":"US","name":"United States"}}},"employee":{"employeeID":"443434","employeeName":"Steve","employeeAddress":{"houseNo":"244","city":"NJ","country":{"code":"US","name":"United States"}}},"assistant":{"assitantID":"443434","employeeName":"Steve","shippingDetails":{"shippingAddress":{"houseNo":"2444","city":"LA","country":{"code":"US","name":"United States"}}}}}
const schema = {
houseNo: null,
country: null,
city: null
}
function match(o1, o2) {
return Object.keys(o1).every(k => k in o2);
}
function get(data, schema) {
return Object.keys(data).reduce((r, e) => {
if (match(data[e], schema)) r.push(data[e]);
else if (typeof data[e] == 'object') r.push(...get(data[e], schema));
return r;
}, [])
}
const result = get(data, schema);
console.log(result)
Here is a plain JS version of one found here
var user = { "user": { "firstName": "John", "lastName": "Steve", "address": { "houseNo": "24", "city": "CA", "country": { "code": "US", "name": "United States" } } }, "employee": { "employeeID": "443434", "employeeName": "Steve", "employeeAddress": { "houseNo": "244", "city": "NJ", "country": { "code": "US", "name": "United States" } } }, "assistant": { "assitantID": "443434", "employeeName": "Steve", "shippingDetails": { "shippingAddress": { "houseNo": "2444", "city": "LA", "country": { "code": "US", "name": "United States" } } } } }
function findProp(obj, prop) {
var result = {};
function recursivelyFindProp(o, keyToBeFound) {
Object.keys(o).forEach(function (key) {
if (typeof o[key] === 'object') {
if (key.toLowerCase().indexOf(keyToBeFound) !==-1) result[key]=o[key];
recursivelyFindProp(o[key], keyToBeFound);
} else {
if (key.toLowerCase().indexOf(keyToBeFound) !==-1) result[key]=o[key];
}
});
}
recursivelyFindProp(obj, prop);
return result;
}
console.log(
findProp(user, "address")
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
Related
I've a collection for user given below
{
"_id": 1
"firstname": "John"
"lastname": "Doe"
"address": {
"street": "13",
"city": "Los Angeles",
"state": "California",
"country": "USA",
"pincode": "12345"
}
}
I want to make an API that will update the user data.
This is what I've tried:
First Method
Using this method some fields are being removed from collection which are not present in request.
Query:
const updateUser = (userId, data) => {
return UserDB.updateOne({ _id: userId }, {
$set: {...data }
});
};
Request:
{
"_id": 1
"firstname": "Justin"
"lastname": "Thomas"
"address": {
"country": "Canada",
"pincode": "9999"
}
}
Result:
// street, city and state is removed from collection
{
"_id": 1
"firstname": "Justin"
"lastname": "Thomas"
"address": {
"country": "Canada",
"pincode": "9999"
}
}
Second Method:
Using this method, fields are set to null which are not present in the request.
Query:
const updateUser = (userId, data) => {
return UserDB.updateOne({ _id: userId }, {
$set: {
"firstname": data.firstname,
"lastname": data.lastname,
"address.street": data.address.street,
"address.city": data.address.city,
"address.state": data.address.state,
"address.country": data.address.country,
"address.pincode": data.address.pincode
}
});
};
Request:
{
"_id": 1
"firstname": "Justin"
"lastname": "Thomas"
"address": {
"country": "Canada",
"pincode": "9999"
}
}
Result:
// street, city and state is set to null in the collection.
{
"_id": 1
"firstname": "Justin"
"lastname": "Thomas"
"address": {
"street": null,
"city": null,
"state": null,
"country": "Canada",
"pincode": "9999"
}
}
Question
How can I update a nested object in a collection?
Second method is working fine playground, please check your data.
Use $addFields in aggregation pipeline of update operation.
$addFields will only update the values that is present in the object and other fields will remain as it is.
Like this:
db.collection.update({
_id: 1
},
[
{
$addFields: {
"firstname": "Justin",
"lastname": "Thomas",
"address": {
"country": "Canada",
"pincode": "16565"
}
}
}
])
Test it here: https://mongoplayground.net/p/pjO8mIKb03o
I am checking for a condition and based on that returning true and false. The thing is my condition even satisfied. Below is my code
$scope.getPostalcode = function(obj) {
for ( var x in obj) {
if (typeof obj[x] === 'object') {
$scope.getPostalcode(obj[x]);
} else if (x === 'postalCode') {
if (obj.postalCode == $scope.postalCode) {
return true;
break;
}
}
}
return false;
}
The value of obj.postalCode and $scope.postalCode is same. Also I have checked the type of both is string. I am also including my object in case I am making the mistake there.
[
{
"account": "10066",
"pcn": "K770",
"msn": "6631",
"meterName": "DM",
"address": {
"companyName": "Test",
"street": "10 Downing Street",
"city": "LONDON",
"country": "UK",
"postalCode": "SW1A 2AA"
}
},
{
"account": "63350",
"pcn": "K7",
"msn": "663",
"meterName": "DM5",
"address": {
"companyName": "DITIQUE",
"street": "10 Downing Street",
"city": "LONDON",
"country": "UK",
"postalCode": "SW1A 2AA"
}
}
]
i have a object which has identifiers:
const cities = {
"0": {
"city": "Bielsko-Biała",
"country": "PL",
},
"1": {
"city": "Kielce",
"country": "PL",
},
"2": {
"city": "Kłodzko",
"country": "PL",
}
}
What i want is to remove 0, 1, 2 and so on to get this format:
const cities = [
{
"city": "Bielsko-Biała",
"country": "PL",
},
{
"city": "Kielce",
"country": "PL",
},
{
"city": "Kłodzko",
"country": "PL",
}
]
It is neccessary to me because i want to add description to every city and i couldn't map in this object because of that format.
I had to do something like that in React and i think it is a very bad written code:
const cities = [];
countries.map(el => {
cities.push(el.city)
})
let updatedCountries = countries;
cities.forEach((city) => {
axios.get(`https://en.wikipedia.org/api/rest_v1/page/summary/${city}`)
.then(response => {
for (let property in updatedCountries) {
if (updatedCountries.hasOwnProperty(property)) {
if (updatedCountries[property].city === city) {
updatedCountries[property]['description'] = response.data.description
}
}
}
})
})
this.setState({
countries: updatedCountries
})
You can use Object.values()
The Object.values() method returns an array of a given object's own enumerable property values,
const cities = {
"0": {
"city": "Bielsko-Biała",
"country": "PL",
},
"1": {
"city": "Kielce",
"country": "PL",
},
"2": {
"city": "Kłodzko",
"country": "PL",
}
}
let values = Object.values(cities);
console.log(values);
var json = [{
"city": "California",
"name": "Joe",
"age": 17,
"type",:"custom"
}, {
"city": "California",
"name": "Bob",
"age": 17,
"type",:"predefined"
}, {
"city": "California",
"name": "Bob",
"age": 35,
"type",:"custom"
}, {
"city": "Texas",
"name": "Bob",
"age": 35,
"type",:"custom"
}, {
"city": "Florida",
"name": "Bob",
"age": 35,
"type",:"predefined"
}];
I have above array and i have to construct object, based on "type" value i.e "predefined" or "custom" as below
updatedjson = {
"predefined": [{
"name": "California",
"count": 1
}, {
"name": "Florida",
"count": 1
}]
"custom": [{
"name": "California",
"count": 2
}, {
"name": "Texas",
"count": 1
}]
}
Any Approach using javascript or lodash
Using Lodash
var json = [{
"city": "California",
"name": "Joe",
"age": 17,
"type": "custom"
}, {
"city": "California",
"name": "Bob",
"age": 17,
"type": "predefined"
}, {
"city": "California",
"name": "Bob",
"age": 35,
"type": "custom"
}, {
"city": "Texas",
"name": "Bob",
"age": 35,
"type": "custom"
}, {
"city": "Florida",
"name": "Bob",
"age": 35,
"type": "predefined"
}];
// Solution to the problem
var updatedJson = _(json).groupBy("type").value(); // #1
_.forOwn(updatedJson, function(value, key) {
let countByCity = _(value).countBy('city').value(); // #2
let res = [];
_.forOwn(countByCity, function(value, key) {
res.push({ // #3
name: key,
count: value
});
});
updatedJson[key] = res;
});
console.log(updatedJson);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
Explanation
Code first groups by type
Then counts by cities, within the group
Finally pushes the count object into an array, in the expected format
You can use reduce & findIndex
var json = [{
"city": "California",
"name": "Joe",
"age": 17,
"type": "custom"
}, {
"city": "California",
"name": "Bob",
"age": 17,
"type": "predefined"
}, {
"city": "California",
"name": "Bob",
"age": 35,
"type": "custom"
}, {
"city": "Texas",
"name": "Bob",
"age": 35,
"type": "custom"
}, {
"city": "Florida",
"name": "Bob",
"age": 35,
"type": "predefined"
}];
var updatedjson = json.reduce(function(result, item) {
// check if type is present in the object
// if not present then add a key by name custom/predefined
if (!result[item.type]) {
result[item.type] = [];
// add city name and intial count
result[item.type].push({
name: item.city,
count: 1
})
} else {
// now find the index of the object whe name matches with current city
let m = result[item.type].findIndex(function(obj) {
return obj.name === item.city
})
// if no matches then add the new object to either custom/predefined
if (m === -1) {
result[item.type].push({
name: item.city,
count: 1
})
} else {
// if matches then increase the value of count
result[item.type][m].count += 1
}
}
return result;
}, {});
console.log(updatedjson)
You can use array.reduce to create a new object with the aggregated data. Example:
const result = json.reduce((obj, data) => {
// if this is a new type, we must initialize it as an empty array and push the first record.
if(!obj[data.type]) {
obj[data.type] = [];
obj[data.type].push({name: data.city, count: 1});
return obj;
}
// else ... check if the current city is present in that type
const existingRecord = obj[data.type].find(d => d.name === data.city);
if(!existingRecord) {
// if the city is not present in that type, we initialize it with count 1
obj[data.type].push({name: data.city, count: 1});
return obj;
}
// if the city is present, we just update count = count + 1;
existingRecord.count++;
return obj;
}, { })
Now, you probably want to extract some of that logic into separate functions to increase readability. I don't think you need lodash for this but in the case you already have it included in your project then feel free to use it :P
Sample JSON Data:
{
"results": [
{
"name": "John Smith",
"state": "NY",
"phone": "555-555-1111"
},
{
"name": "Mary Jones",
"state": "PA",
"phone": "555-555-2222"
},
{
"name": "Edward Edwards",
"state": "NY",
"phone": "555-555-3333"
},
{
"name": "Abby Abberson",
"state": "RI",
"phone": "555-555-4444"
},
]}
With this sample data I can display individual values from the results [] array with object.name and object.phone to look something like:
John Smith 555-555-1111<br />
Mary Jones 555-555-2222<br />
Edward Edwards 555-555-3333<br />
Abby Abberson 555-555-4444
What I am trying to do now is select just the people who's state value is NY and only display their object.name and object.phone:
John Smith 555-555-1111<br />
Edward Edwards 555-555-3333
I tried this lovely little block but all it did was print all the names, which makes sense after I tried it.
if (object.state = "NY") {
div.append(repName);
}
I can't seem to think of a way to only display those that share a the same state.
I'm probably searching for the wrong terms or have to go about this another way... please help!
You are using =(assignment operator),which is wrong.
You have to use ==(comparison operator)
So do like below:-
if (object.state == "NY") {
div.append(repName);
}
Working sample-
var obj = {
"results": [
{
"name": "John Smith",
"state": "NY",
"phone": "555-555-1111"
},
{
"name": "Mary Jones",
"state": "PA",
"phone": "555-555-2222"
},
{
"name": "Edward Edwards",
"state": "NY",
"phone": "555-555-3333"
},
{
"name": "Abby Abberson",
"state": "RI",
"phone": "555-555-4444"
},
]};
$(obj.results).each(function(k,object){
if (object.state == "NY") {
$('#final_data').append(object.name +" : "+object.phone+"<br/>");
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="final_data"></div>
My one cent solution:
var obj = {
"results": [
{
"name": "John Smith",
"state": "NY",
"phone": "555-555-1111"
},
{
"name": "Mary Jones",
"state": "PA",
"phone": "555-555-2222"
},
{
"name": "Edward Edwards",
"state": "NY",
"phone": "555-555-3333"
},
{
"name": "Abby Abberson",
"state": "RI",
"phone": "555-555-4444"
},
]};
obj.results.forEach((value) => {
if (value.state === "NY") {
const li = document.createElement("li");
li.innerHTML = `${value.name} : ${value.phone}`;
document.querySelector("#final_data").appendChild(li);
}
});
<ul id="final_data"></ul>
Like Alive said you used the assignment operator = instead of comparison operator === or ==.