Super Efficient object unwrapping - javascript

I have a snapshot coming from Firebase that I want to turn into a JS class. I currently have a monster loop that is terrible in almost every way. Would anyone have an example of a better way to turn the raw json into this class?
My current method is very long (see section two). I would love to learn a fast more efficient method.
Class:
class SuggestedLocation {
country_slug
region_slug
slug
marker_type
typeInType
geometry
properties
type
id
constructor(country_slug, region_slug, slug, marker_type, typeInType, geometry, properties, type, id) {
this.country_slug = country_slug
this.region_slug = region_slug
this.slug = slug
this.marker_type = marker_type
this.typeInType = typeInType
this.geometry = geometry
this.properties = properties
this.type = type
this.id = id
}
}
Current unwrapping method:
static fromSnapshot(snapshot) {
let suggestedLocations = [new SuggestedLocation()]
if (snapshot.exists()) {
const value = snapshot.val()
const countrySlugs = Object.keys(value)
for (const country_slug of countrySlugs) {
const regionSlugs = Object.keys(value[country_slug])
for (const region_slug of regionSlugs) {
const slugs = Object.keys(value[country_slug][region_slug])
for (const slug of slugs) {
const markerTypes = Object.keys(value[country_slug][region_slug][slug])
for (const markerType of markerTypes) {
const accomAmenityTypes = Object.keys(value[country_slug][region_slug][slug][markerType])
for (const accomAmenityType in accomAmenityTypes) {
const typeInTypes = Object.keys(value[country_slug][region_slug][slug][markerType][accomAmenityType])
for (const typeInType of typeInTypes) {
const ids = Object.keys(value[country_slug][region_slug][slug][markerType][accomAmenityType][typeInType])
for (const id in ids) {
const geoJsonObject = value[country_slug][region_slug][slug][markerType][accomAmenityType][typeInType][id]
const properties = geoJsonObject["properties"]
const geometry = geoJsonObject["geometry"]
const type = geoJsonObject["type"]
suggestedLocations.push(new SuggestedLocation(country_slug, region_slug, slug, markerType, typeInType, geometry, properties, type, id))
}
}
}
}
}
}
}
}
return new SuggestedLocationsObject(suggestedLocations)
}
Example Json:
{
"united-kingdom" : {
"calderdale" : {
"rossendale-way" : {
"accommodations" : {
"Campground" : {
"zO3HxZVELbd" : {
"geometry" : {
"coordinates" : [ -2.1901328761018704, 53.65022995288969 ],
"type" : "Point"
},
"properties" : {
"marker-color" : "#6e875f",
"marker-size" : "medium",
"marker-symbol" : "lodging",
"name" : "",
"place_id" : "zO3HxZVELbd",
"plus_code" : ""
},
"type" : "Feature"
}
}
}
}
}
}
}

You will have to drill down your snapshot data any way, but you could use Object.entries instead of Object.keys, so you get the corresponding value in one go.
Also you could use map and flatMap to produce the array of SuggestedLocation instances without explicit push.
If the multiple assignments to this.* bother you, then you could consider changing the constructor signature so it takes an object instead of the individual values. Then you can use Object.assign to transfer those values in one go.
Finally, I don't really get why you want the array of SuggestedLocation instances to have an initial element with all undefined properties ([new SuggestedLocation()]). The only reason I can think of is for the case where snapshot.exists() is false... then you would have an array with this one entry. But when there is data, why would you have this dummy entry included?
Here is the code that expresses the above ideas (in plain JavaScript - not TypeScript):
class SuggestedLocationsObject extends Array {
constructor(arr) {
super();
Object.assign(this, arr);
}
}
class SuggestedLocation {
constructor(obj) {
Object.assign(this, obj);
}
}
function fromSnapshot(snapshot) {
return new SuggestedLocationsObject(
!snapshot.exists()
? [new SuggestedLocation()]
: Object.entries(snapshot.val()).flatMap(([country_slug, regionSlugs]) =>
Object.entries(regionSlugs).flatMap(([region_slug, slugs]) =>
Object.entries(slugs).flatMap(([slug, markerTypes]) =>
Object.entries(markerTypes).flatMap(([markerType, accomAmenityTypes]) =>
Object.entries(accomAmenityTypes).flatMap(([accomAmenityType, typeInTypes]) =>
Object.entries(typeInTypes).flatMap(([typeInType, ids]) =>
Object.entries(ids).map(([id, {properties, geometry, type}]) =>
new SuggestedLocation({country_slug, region_slug, slug, markerType, typeInType, geometry, properties, type, id})
)
)
)
)
)
)
)
);
}
let data = {
"united-kingdom" : {
"calderdale" : {
"rossendale-way" : {
"accommodations" : {
"Campground" : {
"zO3HxZVELbd" : {
"geometry" : {
"coordinates" : [ -2.1901328761018704, 53.65022995288969 ],
"type" : "Point"
},
"properties" : {
"marker-color" : "#6e875f",
"marker-size" : "medium",
"marker-symbol" : "lodging",
"name" : "",
"place_id" : "zO3HxZVELbd",
"plus_code" : ""
},
"type" : "Feature"
}
}
}
}
}
}
};
let snapshot = {
exists() { return true },
val() { return data }
}
let result = fromSnapshot(snapshot);
console.log(result);

Related

JavaScript - Targeting an object value to create another variable

So I have an array which looks like this:
[
{ TransactionValues: '50.00' },
{ TransactionValues: '-77.43' },
{ TransactionValues: '-20.23' },
{ TransactionValues: '200.23' }
]
I am trying to find a way to target the monetary value and create a variable based on the sum of these. When I try to target the "50.00" for example I get "Undefined" and it's still an array.
I'm not exactly sure how I can target it specifically, is it possible? Any help would be appreciated
As per the comments here is the full code (be wary I'm still learning so it's not elegant):
var fs = require('fs');
var parse = require('csv-parse');
var transactionValues = []; //Need an array to hold transactions
var currentTrans = [];
var savingsTrans = [];
//constuctor for transactions
function addData (id, accountType, initiatorType, dateTime, transactions) {
var data = {
"AccountID" : id,
"AccountType" : accountType,
"InitiatorType" : initiatorType,
"DateTime" : dateTime,
"TransactionValues" : transactions
}
transactionValues.push(data); //should add a new line
}
function logTrans (accountType, transactions) {
if (accountType == "CURRENT") {
var cTrans = {
"TransactionValues" : transactions
}
currentTrans.push(cTrans);
}
else {
var sTrans = {
"TransactionValues" : transactions
}
savingsTrans.push(sTrans);
}
};
//parses the csv file, loops each row and adds it to the transactionValue array
var parser = parse({columns: true}, function (err, results) {
console.table(results);
for (const row of results) {
addData(row.AccountID, row.AccountType, row.InitiatorType, row.DateTime, row.TransactionValue );
logTrans(row.AccountType, row.TransactionValue);
}
console.log(transactionValues);
console.log(currentTrans);
console.log(savingsTrans);
});
fs.createReadStream(__dirname+'/testData/customer-1234567-ledger.csv').pipe(parser)
not completely following but at the end of the day you have an array like data below.
you can use filter to target the attribute you want.
you can use map to pull out just the values.
you can use reduce to sum them all up.
run the snippet below to see each step
const data = [
{ TransactionValues: '50.00', AccountType: 'CURRENT' },
{ TransactionValues: '-77.43', AccountType: null},
{ TransactionValues: '-20.23', AccountType: 'CURRENT' },
{ TransactionValues: '200.23', AccountType: null }
];
const CurrentTrans = data.filter((x) => x.AccountType === 'CURRENT');
const SavingTrans = data.filter((x) => x.AccountType !== 'CURRENT');
console.log('CurrentTrans');
console.log(CurrentTrans);
console.log('SavingTrans');
console.log(SavingTrans);
const CurrentTransValues = CurrentTrans.map((x) => parseFloat(x.TransactionValues));
const SavingTransValues = SavingTrans.map((x) => parseFloat(x.TransactionValues));
console.log('CurrentTransValues');
console.log(CurrentTransValues);
console.log('SavingTransValues');
console.log(SavingTransValues);
const TotalCurrentValues = CurrentTransValues.reduce((sum, x) => sum + x);
const TotalSavingValues = SavingTransValues.reduce((sum, x) => sum + x);
console.log('TotalCurrentValues');
console.log(TotalCurrentValues.toFixed(2));
console.log('TotalSavingValues');
console.log(TotalSavingValues.toFixed(2));
So I may have fixed it by using parseFloat in my addData and logTrans functions:
function addData (id, accountType, initiatorType, dateTime, transactions) {
var data = {
"AccountID" : id,
"AccountType" : accountType,
"InitiatorType" : initiatorType,
"DateTime" : dateTime,
"TransactionValues" : parseFloat(transactions)
}
transactionValues.push(data); //should add a new line
}
function logTrans (accountType, transactions) {
if (accountType == "CURRENT") {
var cTrans = parseFloat(transactions);
currentTrans.push(cTrans);
}
else {
var sTrans = parseFloat(transactions);
savingsTrans.push(sTrans);
}
};
Now that seems to of worked. So I can use the "Sum values of objects in array" as suggested before. Thank you everyone :)

Add conditions to an object dynamically from an Array

I have an object like this, that needs to be sent to an API -
var data = {
field : "category"
value : "order"
}
Now, this API also needs some conditions needed to be sent as nested objects. Something like this -
var data ={
field : "category"
value : "order"
AND : {
field : "circuit"
value : "ninth"
OR : {
field : "second"
value : "abc",
//more conditions here possible //
}
}
So, basically, a condition is nested inside a condition.
EDIT:: Note - Each condition added into the conditions array has to be nested into the previous condition
Now, I have an array of conditions -
conditionsArray = [
{filter: "AND", field: "circuit", value: "ninth"},
{filter: "OR", field: "second", value: "abc"}
]
How do I add conditions to data object from this conditionsArray in the most optimal way?
var data = {
field : "category"
value : "order"
}
Thank you for your time.
EDIT: sorry, I forgot to add what I have tried so far -
I have been trying to just form an object but this is by no means a good solution.
const check = () => {
console.log(conditionsArray);
const data: any = { ...fieldValue };
conditionsArray.forEach((condition: any) => {
const { filter, name, value } = condition;
const { AND, OR, NOT } = data;
if (AND || OR || NOT) {
if (AND) {
data["AND"][filter] = { name, value };
}
if (OR) {
data["OR"][filter] = { name, value };
}
if (NOT) {
data["NOT"][filter] = { name, value };
}
} else {
data[filter] = { name, value };
}
});
console.log(data,"log data");
};
You could reduce the array and return the nested object for the next iteration.
const
data = { field: "category", value: "order" },
conditionsArray = [{ filter: "AND", field: "circuit", value: "ninth" }, { filter: "OR", field: "second", value: "abc" }];
conditionsArray.reduce((o, { filter, ...rest }) => o[filter] = rest, data);
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Using recursion :
function addConditions(data, conditions) {
if (conditions.length == 0) { return data; }
var topMostCondition = conditions[0];
var objectToAdd = { field : topMostCondition.field, value : topMostCondition.value };
data[topMostCondition.filter] = addConditions(objectToAdd, conditions.splice(1));
return data;
}
If your conditionsArray items should be added in the order of appearance to data, you may go recursive way like that:
const conditionsArray=[{filter:"AND",field:"circuit",value:"ninth"},{filter:"OR",field:"second",value:"abc"}],
data={field:"category",value:"order"},
makeATree = (o, list, branch) => {
if(!list.length) return o
const [condition, ...restOfConditions] = list,
{filter, ...rest} = condition
return makeATree(
{
...o,
...(
!branch ?
{[filter]: rest} :
{[branch]: {...o[branch], [filter]: rest}}
)
},
restOfConditions,
filter
)
}
console.log(makeATree(data, conditionsArray))
.as-console-wrapper{min-height:100%;}

How to intersection query Real-time Firebase database?

I'm trying to gain an understanding of query in Firebase. I want to pass userId A & B and find out if they are subscribed to a common chatId, it will either return true or false.
How can query both userId and evaluate results for my desired output?
export const checkForExistingChat = (currentUserId, recipient) => {
var IdList = {}
var query = database
.ref(`Chats/${currentUserId}`)
.orderByChild("subscribedToChat")
.once("value", function (dataSnapshot) {
dataSnapshot.forEach(function (childSnapshot) {
const childData = childSnapshot.val();
console.log("childData : ", childData);
});
});
};
Export JSON of Chat
"Chats" : {
"61vtPjp8YVVSzpvexwXMgEHghYf1" : {
"subscribedToChat" : {
"1a555cbf-30b7-4c8f-9986-4252a7620c45" : "1a555cbf-30b7-4c8f-9986-4252a7620c45",
"2d718ea7-eafa-48db-af14-f165f07b3b08" : "2d718ea7-eafa-48db-af14-f165f07b3b08",
"2e4fd8bb-4afb-4229-83ec-5a427fe2731d" : "2e4fd8bb-4afb-4229-83ec-5a427fe2731d",
"35c537ef-57dd-48c5-9350-5d1fe2e9d118" : "35c537ef-57dd-48c5-9350-5d1fe2e9d118",
"3a816ac1-6e97-4d66-ae19-77e65f8c2df4" : "3a816ac1-6e97-4d66-ae19-77e65f8c2df4",
}
},
"qqpBNbEa8ZSiCEUlseFeGeiRqzh2" : {
"subscribedToChat" : {
"1a555cbf-30b7-4c8f-9986-4252a7620c45" : "1a555cbf-30b7-4c8f-9986-4252a7620c45",
"35c537ef-57dd-48c5-9350-5d1fe2e9d118" : "35c537ef-57dd-48c5-9350-5d1fe2e9d118"
}
}
}
Since you already know how to load data from Firebase, this is essentially a non-Firebase problem: finding the overlapping keys in two lists of keys.
A quick code snippet:
var json = {
"Chats" : {
"61vtPjp8YVVSzpvexwXMgEHghYf1" : {
"subscribedToChat" : {
"1a555cbf-30b7-4c8f-9986-4252a7620c45" : "1a555cbf-30b7-4c8f-9986-4252a7620c45",
"2d718ea7-eafa-48db-af14-f165f07b3b08" : "2d718ea7-eafa-48db-af14-f165f07b3b08",
"2e4fd8bb-4afb-4229-83ec-5a427fe2731d" : "2e4fd8bb-4afb-4229-83ec-5a427fe2731d",
"35c537ef-57dd-48c5-9350-5d1fe2e9d118" : "35c537ef-57dd-48c5-9350-5d1fe2e9d118",
"3a816ac1-6e97-4d66-ae19-77e65f8c2df4" : "3a816ac1-6e97-4d66-ae19-77e65f8c2df4",
}
},
"qqpBNbEa8ZSiCEUlseFeGeiRqzh2" : {
"subscribedToChat" : {
"1a555cbf-30b7-4c8f-9986-4252a7620c45" : "1a555cbf-30b7-4c8f-9986-4252a7620c45",
"35c537ef-57dd-48c5-9350-5d1fe2e9d118" : "35c537ef-57dd-48c5-9350-5d1fe2e9d118"
}
}
}
};
var keys1 = Object.keys(json.Chats["61vtPjp8YVVSzpvexwXMgEHghYf1"].subscribedToChat);
var keys2 = Object.keys(json.Chats["qqpBNbEa8ZSiCEUlseFeGeiRqzh2"].subscribedToChat);
console.log(keys1, keys2);
var commonKeys = keys1.filter(function(key) {
return keys2.indexOf(key) >= 0;
});
console.log(commonKeys);
This is an O(n^2) algorithm, but I doubt that'll be a concern on the list sizes you're likely to have. If it is a concern, and the lists are sorted, you can keep a cursor in each least, and move forward only through them once to make it an O(2n) algorithm.

How to access object properties of 'key' object reactJS

Would like to output this JSON data
I am struggling to find a way to output this data which I am pulling from Firebase, mostly in that I do not know how to select the objects within the 'date' object. Firebase generates these keys automatically: -LMgzJGM78f0BHbPf8cc.
I am not able to output the properties of the objects named as above^ I have tried using nested for(in) loops.
Here is the code I am using currently:
To pull the data from the database
componentDidMount() {
axios.get('./tasks.json')
.then(response => {
const fetchedTasks = [];
for (let date in response.data) {
fetchedTasks.push({
...response.data[date],
date: date,
});
for (let item in response.data[date]) {
fetchedTasks.push({
...response.data[date[item]],
id: item
})
}
}
this.setState((prevState, props) => {
return {
taskList: fetchedTasks,
loading: false
}
})
})
.catch(error => console.log(error));
}
Mapping the state to a JSX element, only outputs like props.name:
{this.state.taskList.map((array, index) => (
<CompleteTask
key={array.date}
taskName={array.date}
/>
) )
}
Here is an example the data as a JSON file, it is set to the state in my app:
{
"tasks" : {
"09182018" : {
"-LMgzJGM78f0BHbPf8cc" : {
"hours" : 0,
"end" : "2018-09-18T14:02:25.022Z",
"minutes" : 0,
"name" : "sadflkjdsalkf",
"seconds" : 2,
"start" : "2018-09-18T14:02:22.508Z"
},
"-LMgzaEYe0tcCjpxOuPU" : {
"hours" : 0,
"end" : "2018-09-18T14:03:38.635Z",
"minutes" : 0,
"name" : "safd",
"seconds" : 2,
"start" : "2018-09-18T14:03:36.353Z"
}
},
}
}
The properties of the key elements -LMgzaEYe0tcCjpxOuPU I am unsure of how to access, these data are created by another part in my app, should I move to a more shallow state to output the properties of 'hours','name', mintutes etc. or is there a way to access it as it is now?
Many thanks in advance.
Are you asking how to access properties with problematic names like -LMgzJGM78f0BHbPf8cc?
If so, instead of the object.property notation, you can access object properties by the property name using the square brackets syntax:
let obj = { color: 'blue' }
let prop = 'color'
console.log(obj.color);
console.log(obj['color']);
console.log(obj[prop]);
If not, please try to make more clear what your current problem is.
I'd suggest to transform the object received from the Firebase to array in this way:
const formattedTasks = [];
const tasks = Object.values(data.tasks);
tasks.forEach(task =>
Object.entries(task).forEach(([key, value]) =>
formattedTasks.push({ name: key, data: value })
)
);
So, you'll map through formattedTasks array.
Here's a working example: https://codesandbox.io/s/l348nnkv9q
Since the keys in those objects are unknown, it may be useful to use Object.keys(). Try something like this in your JSX:
Given:
const data = {
"tasks" : {
"09182018" : {
"-LMgzJGM78f0BHbPf8cc" : {
"name" : "Task One",
},
"-LMgzaEYe0tcCjpxOuPU" : {
"name" : "Task Two",
}
},
}
};
JSX:
<ul>
{Object.keys(data.tasks).map((date) => {
const dayTasks = tasks[date];
return Object.keys(dayTasks).map((key) => {
const task = dayTasks[key];
return (
<li>{task.name}</li>
)
})
})}
</ul>
<div>
{
Object.entries(slot).map(([key, value]) =>
<div>
{console.log("key", key)}
<span>{key}</span>
<div>
{value.map(g => (
<div>{console.log("g", g.eTime)}
<span>{g.eTime}</span>
</div>
))}
</div>
</div>
)
}
</div>

Merge JS sub-objects into a JS object with joined keys

I'm writing template software for publishing node.js modules to GitHub and NPM. A small snippet of an example template:
{{module.name}}
{{module.description}}
Purpose
{{module.code.purpose}}
I have an object built up with all these properties, ex.
{
"module" : {
"name" : "test",
"description" : "Test module"
...
The problem is that I need to merge this object's sub-objects so the final output looks something like this (for replacing the placeholders in the templates):
{
"module.name" : "test",
"module.description" : "Test module"
...
So I created my own function to do that:
/**
Takes an object like
{
"type" : "meal",
"pizza" : {
"toppings" : ["pepperoni", "cheese"],
"radius" : 6,
"metadata" : {
"id" : 24553
}
}
}
and changes it to
{
"type" : "meal",
"pizza.toppings" : ["pepperoni", "cheese"],
"pizza.radius" : 6,
"pizza.metadata.id" : 244553
}
*/
const mergeObjectsToKeys = object => {
// Loop through the passed in object's properties
return Object.entries(object).reduce((obj, [key, value]) => {
// Check if it is a sub-object or not
if (typeof value === "object") {
// If it is a sub-object, merge its properties with recursion and then put those keys into the master object
const subObject = mergeObjectsToKeys(value);
Object.entries(subObject).forEach(([key2, value2]) => {
obj[key + "." + key2] = value2;
});
} else {
// Otherwise, just put the key into the object to return
obj[key] = value;
}
}, { });
};
Two questions
Is this the correct way to write the software?
If so, is there a built-in function to merge the sub-objects, like shown above?
One built-in function to handle the requirement is Object.assign(); you can use spread element, Object.entries(), .map() to set property names of object which is property of object.
To handler objects where value is not nested object
let p = Object.keys(o).pop();
let res = Object.assign({}, ...Object.entries(o.module).map(([key, prop]) =>
({[`${p}.${key}`]: prop})));
To handle value which is nested object
let o = {
"type": "meal",
"pizza": {
"toppings": ["pepperoni", "cheese"],
"radius": 6,
"metadata": {
"id": 24553
}
}
}
let res = Object.assign({}, ...Object.entries(o).map(([prop, value]) =>
typeof value === "object" && !Array.isArray(value)
? Object.assign({}, ...Object.entries(value)
.map(([key,val]) => ({[`${prop}.${key}`]: key})))
: {[prop]: value})
);
console.log(res);

Categories

Resources