How to write a function which represents an object lookup path - javascript

Write a function that takes a object and a string, which represents an object lookup path, for example "something1.something2". The function should return the value on the specific path.
Example:
const lookup = (obj, path) => {....}
const obj = { something1: { something2: "Mouse", something3: 'Cat' } };
const path = 'something1.something2'
console.log(lookup(obj, path));
Result:
'Mouse'

You can use split and then reference the property dynamically using square brackets
const lookup = (obj, path) => {
const paths = path.split('.');
return obj[paths[0]][paths[1]];
}
const obj = { something1: { something2: "Mouse", something3: 'Cat' } };
const path = 'something1.something2'
console.log(lookup(obj, path));

Related

Programmatically flatten yaml with typescript

Example input (generally, any yaml file with any content)
some:
nested:
entry: paris
another:
nested:
entry: tel-aviv
Desired output:
Map(2) {
'.some.nested.entry' => 'paris',
'.another.nested.entry' => 'tel-aviv'
}
I tried to use recursion, but failed to define the base case (the function atom):
import { readFileSync } from 'fs'
import { parse, stringify } from 'yaml'
const yamlData = parse(readFileSync('input.yaml').toString())
// An ugly hack to demonstrate what I want to achieve
function atom(yamlData): boolean {
const stringified = stringify(yamlData)
const distance = stringified.search('entry')
const atomicity = distance === 0;
return stringified.search('entry') < 3 // obviously not the way ...
}
function flatten(yamlData, prefix: string): Map<string, any>
{
const flattened = new Map<string, any>();
for (const key in yamlData) {
if (atom(yamlData[key])) {
const abskey = prefix + "." + key;
const value = stringify(yamlData[key]);
const a = value.split(": ")[0].trim() // <--- ugly ...
const b = value.split(": ")[1].trim() // <--- ugly ...
flattened.set(abskey + "." + a, b)
continue;
}
const flattenedTag = flatten(yamlData[key], prefix + "." + key)
for (const [keyTag, value] of flattenedTag.entries()) {
flattened.set(keyTag, value)
}
}
return flattened
}
const flattened = flatten(yamlData, "");
console.log(flattened)

Creates an object composed of the picked object properties: [duplicate]

This question already has answers here:
Filter object properties by key in ES6
(30 answers)
Closed 1 year ago.
I'm trying to create an object from object and list of properties.
const pick = (obj, ...fields) => {
return [...fields] = obj
};
How can I realise this?
Reduce the list of fields, and take the values from the original object:
const pick = (obj, ...fields) => fields.reduce((acc, field) => ({ ...acc, [field]: obj[field] }), {});
const obj = { a: 1, b: 2, c: 3 };
const result = pick(obj, 'a', 'c');
console.log(result);
You can use the in operator to ignore properties that don't exist on the original object:
const pick = (obj, ...fields) => fields.reduce((acc, field) => {
const value = obj[field];
if(field in obj) acc[field] = value;
return acc;
}, {});
const obj = { a: 1, b: 2, c: 3 };
const result = pick(obj, 'a', 'c', 'd');
console.log(result);
Try something like this:
const pick = (obj, ...fields) => Object.fromEntries(Object.entries(obj).filter(([k]) => fields.includes(k)))
Iterate through fields array and check if property is available in obj then put into final object which needs to be returned.
const pick = (obj, ...fields) => {
const finalObj = { };
for (field of fields) {
if (obj[field]) {
finalObj[field] = obj[field];
}
}
return finalObj;
};
const obj = { name: "test name", age: 25, title: "Mr.", city: "test city" };
console.log(pick(obj, "name", "age", "city", "other"));

find the next path from the list

How can i reduce this logic into much more better one like a reusable hook function, where i can pass the current path and the tabsDetails and it returns me the next path so I can push to the next route once user clicks on next button
const tabsDetails = [
{
path: "/user-details",
},
{
path: "/service-details",
},
{
path: "/previous-experience-details",
},
{
path: "/documents",
},
];
const allowNextRoute = () => {
// early return if tabsDetails is having falsy values or length is 0
if (!tabsDetails?.length) return
const { pathname } = useLocation() //'details/11012/user-details'
const currentPathsSplitted = pathname?.split("/")
const currentPath = currentPathsSplitted.reverse()[0]
const filteredPaths = tabsDetails.map(({ path }) => path)
let currentPathIndex = filteredPaths.indexOf(`/${currentPath}`)
let nextPathIndex = ++currentPathIndex % filteredPaths.length
let nextPath = filteredPaths[nextPathIndex]
currentPathsSplitted.reverse().pop()
currentPathsSplitted.push(nextPath.split("/").reverse()[0])
history.push(currentPathsSplitted.join("/")) // 'details/11012/service-details
}
You can use pop and findIndex to reduce the array gymnastics:
const tabsDetails = [{path: "/user-details"},{path: "/service-details"},{path: "/previous-experience-details"},{path: "/documents"}];
const delim = "/";
const allowNextRoute = (journey, resource) => {
if (!journey) return;
const parts = resource.split(delim);
const current = `${delim}${parts.pop()}`;
const path = parts.join(delim);
const currIndex = journey.findIndex(o => o.path === current);
const test = (currIndex < journey.length - 1) && (currIndex > -1);
return test ? `${path}${journey[currIndex + 1].path}` : "";
}
let path;
console.log("Test 1 - OP");
path = "details/11012/user-details"; // useLocation()
while (path) {
console.log(path);
path = allowNextRoute(tabsDetails, path);
}
console.log("Test 2 - no matching path");
path = "details/11012/user-details-error"; // useLocation()
while (path) {
console.log(path);
path = allowNextRoute(tabsDetails, path);
}
console.log("Test 3 - no details");
path = "details/11012/user-details"; // useLocation()
while (path) {
console.log(path);
path = allowNextRoute([], path);
}
console.log("Test 4 - details with no match");
path = "details/11012/user-details"; // useLocation()
while (path) {
console.log(path);
path = allowNextRoute([{path: "/foo"}], path);
}
I would personally start with transforming the structure into one that supports the logic you attempt to deploy.
Instead of using an array you might want to transform the current data in a more usable structure that simplifies path lookup and finding the next item. One such structure could be a "linked list" where you have a "node" with the structure:
{ item: item, next: nextNode }
Then use an object to make lookups a specific paths fast:
const tabDetailsLookup = {
"/user-details": {
item: { path: "/user-details" }
next: -> ref to "/service-details" node
},
"/service-details": {
item: { path: "/service-details" }
next: -> ref to "/previous-experience-details" node
},
// ...
}
You can build this structure using a simple for-loop:
const tabsDetails = [
{ path: "/user-details" },
{ path: "/service-details" },
{ path: "/previous-experience-details" },
{ path: "/documents" },
];
// prepare data structure to fit logic needs
const tabDetailsLookup = (function () {
// place array elements inside nodes
const nodes = tabsDetails.map(item => ({ item }));
const lookup = {};
for (let i = 0; i < nodes.length; ++i) {
// create a reference from the current node to the next node
nodes[i].next = nodes[(i + 1) % nodes.length];
// add the current node to the lookup object
lookup[nodes[i].item.path] = nodes[i];
}
return lookup;
})();
console.log(tabDetailsLookup);
// this structure makes it a lot easier to find the next path
console.log(tabDetailsLookup["/user-details"].next.item.path);
This simplifies:
const filteredPaths = tabsDetails.map(({ path }) => path)
let currentPathIndex = filteredPaths.indexOf(`/${currentPath}`)
let nextPathIndex = ++currentPathIndex % filteredPaths.length
let nextPath = filteredPaths[nextPathIndex]
Into:
const nextPath = tabDetailsLookup[`/${currentPath}`].next.item.path;
You can supplement this with a function that does the other stuff:
function nextPath(path) {
const parts = path.split("/");
if (parts.length == 0) return;
const current = parts.pop();
const node = tabDetailsLookup[`/${current}`];
if (!node) return;
return parts.join("/") + node.next.item.path;
}
const tabsDetails = [
{ path: "/user-details" },
{ path: "/service-details" },
{ path: "/previous-experience-details" },
{ path: "/documents" },
];
// prepare data structure to fit logic needs
const tabDetailsLookup = (function () {
// place array elements inside nodes
const nodes = tabsDetails.map(item => ({ item }));
const lookup = {};
for (let i = 0; i < nodes.length; ++i) {
// create a reference from the current node to the next node
nodes[i].next = nodes[(i + 1) % nodes.length];
// add the current node to the lookup object
lookup[nodes[i].item.path] = nodes[i];
}
return lookup;
})();
function nextPath(path) {
const parts = path.split("/");
if (parts.length == 0) return;
const current = parts.pop();
const node = tabDetailsLookup[`/${current}`];
if (!node) return;
return parts.join("/") + node.next.item.path;
}
console.log(nextPath("details/11012/user-details"));
console.log(nextPath("details/11012/service-details"));
console.log(nextPath("non-existent"));

Get object in array that contains closest matching id property in a string?

I am wondering if there is a better way to do get the result.
I have an array of objects, each object contains an id as a string path pattern. I want to return the object that has the best match to a url path. ATM I am using lodash
All id's are unique.
const url = '/economia/finanzas/moodys-coloca-calificaciones-de-riesgo-de-costa/JZF24QAQHBBFPLJQL5VZJPKCZA/story/'
const sites = [{
'_id': '/la-nacion/economia'
}, {
'_id': '/la-nacion'
}, {
'_id': '/la-nacion/economia/finanzas'
}, {
'_id': '/la-nacion/economia/moodys'
}]
const urlArr = url.split('/')
const compare = sites.map(site => {
// get all matches
const siteArr = site._id.split('/')
// get lengths of matches
return _.intersection(siteArr, urlArr).length
})
// get index of obj with best match
const indexOfBestMatch = _.indexOf(compare, _.max(compare))
// new primary section
const newPrimarySection = sites.filter((e, i) => {
return i === indexOfBestMatch
})
console.log(newPrimarySection)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
https://jsbin.com/lumepereyi/1/edit?js,console
No need for a library, you can use reduce to iterate over the array of _ids, keeping a count of the number of substring matches, so that it resolves to the one with the most matches:
const url = '/economia/finanzas/moodys-coloca-calificaciones-de-riesgo-de-costa/JZF24QAQHBBFPLJQL5VZJPKCZA/story/';
const sites = [{
'_id': '/la-nacion/economia'
}, {
'_id': '/la-nacion'
}, {
'_id': '/la-nacion/economia/finanzas'
}, {
'_id': '/la-nacion/economia/moodys'
}];
const substrings = new Set(url.split('/'));
const countMatches = str => str.split('/').reduce((a, substr) => a + (substrings.has(substr)), 0);
const { bestMatch } = sites.reduce(({ bestMatch, count=0 }, { _id }) => {
const thisCount = countMatches(_id);
return thisCount > count
? { count: thisCount, bestMatch: _id }
: { count, bestMatch };
}, {});
console.log(bestMatch);
Since you only need the item with the max matches, you can use _.maxBy() to iterate the array of sites, and extract the item. Use _.get() to extract the value of _id, because _.get() won't throw an error if sites is empty:
const url = '/economia/finanzas/moodys-coloca-calificaciones-de-riesgo-de-costa/JZF24QAQHBBFPLJQL5VZJPKCZA/story/'
const sites = [{"_id":"/la-nacion/economia"},{"_id":"/la-nacion"},{"_id":"/la-nacion/economia/finanzas"},{"_id":"/la-nacion/economia/moodys"}]
const getPrimarySection = (url, sites) => {
const urlArr = url.split('/')
return _.get(_.maxBy(sites, site => {
const siteArr = site._id.split('/')
return _.intersection(siteArr, urlArr).length
}), '_id')
}
const newPrimarySection = getPrimarySection(url, sites)
console.log(newPrimarySection)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
If your path prefix is always the same (like it seems it is with /la-naction then you could do your own scoring (via startsWith) based on the length of the matched string and then sort-by desc (biggest score) ... and take the top score:
const url = '/economia/finanzas/moodys-coloca-calificaciones-de-riesgo-de-costa/JZF24QAQHBBFPLJQL5VZJPKCZA/story/'
const sites = [{"_id":"/la-nacion/economia"},{"_id":"/la-nacion"},{"_id":"/la-nacion/economia/finanzas"},{"_id":"/la-nacion/economia/moodys"}]
const getBestMatch = (s, u, p = '/la-nacion') => { // <-- default prefix
const topScored = s.map(x =>
(Object.assign(x, {
score: ((`${p}${u}`).startsWith(x._id) ? x._id.length : 0)}), x)
).sort((a, b) => b.score - a.score)[0] // <-- sort, get the highest score
return topScored.score > 0 ? topScored._id : undefined
}
console.log(getBestMatch(sites, url))
No lodash neeed etc it is just map to add the score and then sort really.

Create object with only defined properties

I have a function which returns an object with properties only which are defined.
How to refactor the function so that I don't need to make if clauses for every parameter value? There must be more elegant way to do this.
const getQuery = ({ foo, bar, zoo }) => {
const query = {};
if (foo) {
query.foo = foo;
}
if (bar) {
query.bar = bar;
}
if (zoo) {
query.zoo = zoo;
}
return query;
}
I would do something like
function getQuery(obj){
// filter the accepted keys
var filtered = Object.keys(obj).filter((k) => ~["foo", "bar", "baz"].indexOf(k))
// construct new object with filtered keys
var query = {}
filtered.forEach((k) => query[k] = obj[k])
return query
}
Here's a basic function that will copy only properties provided in the wantedProps array. It will not mutate the original object.
let filterProperties = (originalObject = {}, wantedProps = []) =>
{
let filteredObject = {};
wantedProps.forEach( val => filteredObject[val] = originalObject[val] );
return filteredObject;
}
If you're just trying to filter out undefined vals then you could do:
obj => {
let newObject = {}
Object.keys(obj).forEach(key => {
if(obj[key] !== undefined) newObject[key] = obj[key];
})
return newObject;
}

Categories

Resources