I have this Javascript object (that is created on-the-fly by my plugin code):
{
"field": {
"name": "Name",
"surname": "Surname"
},
"address": {
"street": "Street",
"number": 0,
"postcode": 0,
"geo": {
"city": "City",
"country": "Country",
"state": "State"
}
},
"options": [1,4,6,8,11]
}
I don't want to turn this object to a JSON string, but I want to turn this object into another object, but with each field represented by a string, like this:
{
"field[name]": "Name",
"field[surname]": "Surname",
"address[street]": "Street",
"address[number]": 0,
"address[postcode]": 0,
"address[geo][city]": "City",
"address[geo][country]": "Country",
"address[geo][state]": "State",
"options[0]":1,
"options[1]":4,
"options[2]":6,
"options[3]":8,
"options[4]":11
}
Scenario:
I dont know how the original object will look like (or how deep it'll be), since it's part of a plugin and I have no idea how people will build their forms
I'm going to put this new object inside a FormData object, if it would only accept objects, it would be easier, because JSON can't upload files, but FormData object can
As I said in the comments, you need a for...in [MDN] loop to iterate over the properties of the object and can use recursion to subsequently convert nested objects:
function convert(obj, prefix, result) {
result = result || {};
// iterate over all properties
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
var value = obj[prop];
// build the property name for the result object
// first level is without square brackets
var name = prefix ? prefix + '[' + prop + ']' : prop;
if (typeof value !== 'object') {
// not an object, add value to final result
result[name] = value;
}
else {
// object, go deeper
convert(value, name, result);
}
}
}
return result;
}
// Usage:
var converted_data = convert(data);
DEMO
Still, I would recommend using JSON.
If you want to handle files as well, you might have to add an additional check for File objects. You'd want them raw in the result object:
else if (window.File && value instanceof File) {
result[name] = value;
}
// and for file lists
else if (window.FileList && value instanceof FileList) {
for (var i = 0, l = value.length; i < l; i++) {
result[name + '[' + i + ']'] = value.item(i);
}
}
It could be that the File (FileList) constructor is named differently in IE, but it should give you a start.
Not a big fan of reinventing the wheel, so here is how you could answer your question using object-scan. It's a great tool for data processing - once you wrap your head around it that is.
// const objectScan = require('object-scan');
const convert = (haystack) => objectScan(['**'], {
filterFn: ({ key, value, isLeaf, context }) => {
if (isLeaf) {
const k = key.map((e, idx) => (idx === 0 ? e : `[${e}]`)).join('');
context[k] = value;
}
}
})(haystack, {});
const data = { field: { name: 'Name', surname: 'Surname' }, address: { street: 'Street', number: 0, postcode: 0, geo: { city: 'City', country: 'Country', state: 'State' } }, options: [1, 4, 6, 8, 11] };
console.log(convert(data));
/* =>
{ 'options[4]': 11,
'options[3]': 8,
'options[2]': 6,
'options[1]': 4,
'options[0]': 1,
'address[geo][state]': 'State',
'address[geo][country]': 'Country',
'address[geo][city]': 'City',
'address[postcode]': 0,
'address[number]': 0,
'address[street]': 'Street',
'field[surname]': 'Surname',
'field[name]': 'Name' }
*/
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan
Related
I'm trying to split this object based on a common new property 'subject'.
There could be multiple subjects in a single object in the original data. But in the final data, it must have a single subject and the marks associated with it.
let x = {
'Name': 'Ajay',
'Maths': 0,
'English': 26,
}
and the expected output is
let y = [{
'Name' : 'Ajay',
'Marks' : 0,
'Subject' : 'Maths'
},{
'Name' : 'Ajay',
'Marks' : 26,
'Subject' : 'English'
}]
It's basically splitting each object into multiple objects.
Could anyone help me out with an approach to this?
I went with this approach in the end of iterating through the keys and skipping 'name' property.
for (let i of Object.keys(x)) {
let temp = {};
if (i === "Name") {
continue;
} else {
temp = {
Name: x["Name"],
Subject: i,
Marks: x[i],
};
}
You could destructure the common property and map the other entries.
const
convert = ({ Name, ...o }) => Object
.entries(o)
.map(([Subject, Marks]) => ({ Name, Subject, Marks })),
data = { Name: 'Ajay', Maths: 0, English: 26 },
result = convert(data);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use object destructuring to extract name and then process remaining properties. Object.entries gives you key-value pairs:
let x = {
'Name': 'Ajay',
'Maths': 0,
'English': 26,
};
const { Name, ...subjects } = x;
const result = Object.entries(subjects).map(([Subject, Marks]) => ({Name, Subject, Marks}));
console.log(result);
Overview: I have an array (arr) of objects. I need to convert any value in an object that looks like an ISO timestamp to an epoch. I match via regular expression.
I have an object that looks like:
{
assignee: null
color: null
description: "cool object"
id: "12234858"
last_updated: "2021-01-22T15:30:10.495000+00:00"
}
I have some code that does the following:
arr.forEach((item) => {
let regex = /^[+-]?\d{4}(-[01]\d(-[0-3]\d(T[0-2]\d:[0-5]\d:?([0-5]\d(\.\d+)?)?[+-][0-2]\d:[0-5]\dZ?)?)?)?/;
Object.entries(myobject).forEach(([key, value]) => {
if (value !== null) {
if (value.match(regex)) {
value = Date.parse(value) / 1000;
}
}
});
});
The date conversions work while iterating through the key, values of each object, but once the arr.forEach() completes, the values are reset. What is the best way to handle these conversions? Should I map to new object and duplicate?
The value itself is a primitive that doesn't retain "connection" to the object it was taken from. You need to assign the value back to the item using the key you've destructured.
Note: your regex doesn't actually work, and detects other values as dates. It's easier just to parse, and check if the value is NaN. See this answer.
const arr = [{
assignee: null,
color: null,
description: "cool object",
id: "12234858",
last_updated: "2021-01-22T15:30:10.495000+00:00",
}];
arr.forEach((item) => {
Object.entries(item).forEach(([key, value]) => {
if (!isNaN(Date.parse(value || ''))) {
// assign the new value back to the original item
item[key] = Date.parse(value) / 1000;
}
});
});
console.log(arr);
As noted by #NicholasCarey's comment, !isNaN(Date.parse()) might identify other strings as dates. So if you are experiencing false positives, you might want to use regex (taken from this answer) or a stricter library:
const arr = [{
assignee: null,
color: null,
description: "cool object",
id: "12234858",
last_updated: "2021-01-22T15:30:10.495000+00:00",
}];
const regex = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/;
arr.forEach((item) => {
Object.entries(item).forEach(([key, value]) => {
if (regex.test(value || '')) {
// assign the new value back to the original item
item[key] = Date.parse(value) / 1000;
}
});
});
console.log(arr);
I would use a library like Luxon. Like so:
const {DateTime} = require('luxon');
function transformIso8601ToSomethingUsable(arr) {
for ( const obj of arr ) {
for ( const [k,v] of Object.entries(obj) ) {
obj[k] = iso8601DateTime2epochTime(v);
}
}
}
function iso8601DateTime2epochTime(s) {
const dt = DateTime.fromISO(s).toUTC();
const epochTime = dt.isValid
? Math.round(dt.toSeconds())
: s ;
return epochTime;
}
Then you can say something like
const arr = [{
assignee: null,
color: null,
description: "cool object",
id: "12234858",
last_updated: "2021-01-22T15:30:10.495000+00:00",
}];
transformIso8601ToSomethingUsable(arr);
and get
[
{
"assignee": null,
"color": null,
"description": "cool object",
"id": "12234858",
"last_updated": 1611329410
}
]
I'm making Ajax calls to a page in ASP.NET Core 3.1.
The response is a JsonResult whose Value property is an instance of a custom class, itself containing various string and collection properties.
One of these collections is a Dictionary<string, string>, which I can then access in JavaScript along the following lines:
var dictionary = response.DictionaryObj;
for (key in dictionary) {
DoSomeStuff(key, dictionary[key]);
}
However another of these collections requires a non-unique 'key', and is currently a List<KeyValuePair>
This ends up in JavaScript as an array of objects, which I can access like this:
var kvps = response.KvpList;
for (i = 0; i < kvps.length; i++) {
var kvp = kvps[i];
DoSomeMoreStuff(kvp.key, kvp.value);
}
The latter seems far less elegant - is there a way of packaging up the KeyValuePairs in a way that would let me use the former syntax?
For Dictionary<string, string> you can use Object.entries()
For List<KeyValuePair> object destructuring
const dictionaryObj = {
a: 'somestring',
b: 42,
};
for (const [key, value] of Object.entries(dictionaryObj)) {
console.log(`${key}: ${value}`); // DoSomeStuff(key, value)
}
console.log('===========================================');
const kvpList = [
{ key: '1', value: 'v1' },
{ key: '2', value: 'v2' },
{ key: '3', value: 'v3' },
];
for (const { key, value } of kvpList) {
console.log(`${key}: ${value}`); // DoSomeMoreStuff(key, value)
}
If you have an object and you want to iterate through its properties, then we can use Object.entries method to get an array of a given object's own enumerable string-keyed property [key, value] pairs, and then just use loop foreach:
let input = { "workType": "NDB To Nice", "priority": 5, "name": "Joseph", "lastName": "Skeet" }
const fooFunctiion = (key, value) => {
console.log(`key: ${key}, value ${value}` )
}
Object.entries(input).forEach(([k, v]) => {
fooFunctiion(k, v)
});
If you have an array of objects, then you can use foreach method:
let input = [
{ "workType": "NDB To Nice", "priority": 5 },
{ "workType": "PDAD", "priority": 0 },
{ "workType": "PPACA", "priority": 0 },
{ "workType": "Retrigger", "priority": "5" },
{ "workType": "Special Intake Request Intake", "priority": "7" }
];
const fooFunction = (obj, index) => {
console.log('obj: ', obj, index )
}
input.forEach((obj, ind) =>
fooFunction(obj, ind)
);
Hello guys I have two arrays
var elements = [{
"id": "id_1",
"type": "input",
"businesstype": { "type": "text" }
},
{
"type": "label",
"id": "id_234"
},
{
"id": "id_16677",
"type": "div",
},
{
"id": "id_155",
"type": "input",
"businesstype": { "type": "password" }
}
]
var filterArray=[{type:'input',businesstype:{type:'text'}},{type:'div'}]
and want common obejct like
var output = [{
"id": "id_1",
"type": "input",
"businesstype": { "type": "text" }
},
{
"id": "id_16677",
"type": "div",
}
]
How do I compare these two objects to get equal objects from elements.
You could filter it with a recursive approach for the nested objects.
const isObject = o => o && typeof o === 'object',
isEqual = (f, o) =>
isObject(o) && Object.keys(f).every(k =>
isObject(f[k]) && isEqual(f[k], o[k]) || o[k] === f[k]
);
var elements = [{ id: "id_1", type: "input", businesstype: { type: "text" } }, { type: "label", id: "id_234" }, { id: "id_16677", type: "div" }, { id: "id_155", type: "input", businesstype: { type: "password" } }],
filterArray = [{ type: 'input', businesstype: { type: 'text' } }, { type: 'div' }],
result = elements.filter(o => filterArray.some(f => isEqual(f, o)));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If your filterArray does not have further objects in its hierarchy, you can make do with this solution - see demo below:
var elements=[{id:"id_1",type:"input",businesstype:{type:"text"}},{type:"label",id:"id_234"},{id:"id_16677",type:"div"},{id:"id_155",type:"input",businesstype:{type:"password"}}],filterArray=[{type:"input",businesstype:{type:"text"}},{type:"div"}];
var result = elements.filter(function(e) {
return filterArray.some(function(f) {
return Object.keys(f).every(function(k) {
return e.hasOwnProperty(k) && Object.keys(f[k]).every(function(n) {
return e[k][n] == f[k][n];
});
});
});
});
console.log(result);
.as-console-wrapper {top: 0;max-height: 100%!important;}
(Since you tagged Ramda)
Ramda already has many useful (object) comparison functions you can use to make the filter a bit easier to read. (i.e.: equals and other functions that use it under the hood, like contains)
You could, for example, write:
const elements=[{id:"id_1",type:"input",businesstype:{type:"text"}},{type:"label",id:"id_234"},{id:"id_16677",type:"div"},{id:"id_155",type:"input",businesstype:{type:"password"}}];
const filterArray=[{type:'input',businesstype:{type:'text'}},{type:'div'}];
// Describes how to define "equality"
// i.e.: elements are equal if type and businesstype match
// e.g.: pick(["a", "b"], { a: 1, b: 2, c: 3}) -> { a: 1, b: 2}
const comparisonObjectFor = pick(["type", "businesstype"]);
// Compares an object's comparison representation to another object
const elEquals = compose(whereEq, comparisonObjectFor);
// Creates a filter method that searches an array
const inFilterArray = matchElements => el => any(elEquals(el), matchElements);
// Run the code on our data
filter(inFilterArray(filterArray), elements);
Running example here
I don't think this is necessarily the best solution (in terms of reusability, readability), but I'd advice you to not inline deep object/array comparison methods since:
You're probably going to use them more than once
They are hard to understand/predict if not given the right name & documentation
They are prone to (small) bugs because of their complexity
In other words: since you've tagged lodash and Ramda, I can safely advice to use a well tested, well used library for the comparison of your objects.
For a nested complex object or array, I would like to collect all values for a given property name. Example:
var structure = {
name: 'alpha',
array: [
{ name: 'beta' },
{ name: 'gamma' }
],
object: {
name: 'delta',
array: [
{ name: 'epsilon' }
]
}
};
// expected result: [ 'alpha', 'beta', 'gamma', 'delta', 'epsilon' ]
It's obvious how to achieve this using plain JS, but: Is there any elegant, concise approach using lodash?
[edit] Current variant below. Nicer solutions welcome!
function getPropertyRecursive(obj, property) {
var values = [];
_.each(obj, function(value, key) {
if (key === property) {
values.push(value);
} else if (_.isObject(value)) {
values = values.concat(getPropertyRecursive(value, property));
}
});
return values;
}
This can be done elegantly with the following mixin, which is a recursive version of _.toPairs:
_.mixin({
toPairsDeep: obj => _.flatMap(
_.toPairs(obj), ([k, v]) =>
_.isObjectLike(v) ? _.toPairsDeep(v) : [[k, v]])
});
then to get the result you want:
result = _(structure)
.toPairsDeep()
.map(1)
.value()
If there are scalar properties other than name, you'll have to filter them out:
result = _(structure)
.toPairsDeep()
.filter(([k, v]) => k === 'name')
.map(1)
.value()
There's no Lodash/Underscore function that I know if that will do what you're looking for.
So what are you looking to do? Well, specifically you're looking to extract the values of all of the name properties out of a aggregate structure. How would we generalize that? In other words, if you were looking to add such functionality to Lodash/Underscore, how would you reframe the problem? After all, most people don't want to get the values of the name properties. You could create a generic function where you supply the name of the property you want, but...thinking even more abstractly than that, what you really want to do is visit all of the nodes in a aggregate structure and do something with them. If we consider aggregate structures in JavaScript as generic trees, we can take a recursive approach using a depth-first walk:
function walk(o, f) {
f(o);
if(typeof o !== 'object') return;
if(Array.isArray(o))
return o.forEach(e => walk(e, f));
for(let prop in o) walk(o[prop], f);
}
Now we can do what you're looking for by walking the structure and adding things to an array:
const arr = [];
walk(structure, x => if(x !== undefined && x.name) arr.push(x.name));
This isn't quite functional enough for my tastes, though...there's a side effect on arr here. So an even better generic approach (IMO) would be to allow a context object to ride along (or an accumulator if you will, a la Array#reduce):
function walk(o, f, context) {
f(o, context);
if(typeof o !== 'object') return context;
if(Array.isArray(o)) return o.forEach(e => walk(e, f, context)), context;
for(let prop in o) walk(o[prop], f, context);
return context;
}
Now you can call it like this, side-effect free:
const arr = walk(structure, (x, context) => {
if(x !== undefined && x.name) context.push(x.name);
}, []);
Iterate the object recursively using _.reduce():
function getPropertyRecursive(obj, prop) {
return _.reduce(obj, function(result, value, key) {
if (key === prop) {
result.push(value);
} else if (_.isObjectLike(value)) {
return result.concat(getPropertyRecursive(value, prop));
}
return result;
}, []);
}
var structure = {
name: 'alpha',
array: [{
name: 'beta'
}, {
name: 'gamma'
}],
object: {
name: 'delta',
array: [{
name: 'epsilon'
}]
}
};
var result = getPropertyRecursive(structure, 'name');
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.2/lodash.min.js"></script>
You could iterate the object and call it again for arrays or objects. Then get the wanted property.
'use strict';
function getProperty(object, key) {
function iter(a) {
var item = this ? this[a] : a;
if (this && a === key) {
return result.push(item);
}
if (Array.isArray(item)) {
return item.forEach(iter);
}
if (item !== null && typeof item === 'object') {
return Object.keys(item).forEach(iter, item);
}
}
var result = [];
Object.keys(object).forEach(iter, object);
return result;
}
var structure = { name: 'alpha', array: [{ name: 'beta' }, { name: 'gamma' }], object: { name: 'delta', array: [{ name: 'epsilon' }] } };
console.log(getProperty(structure,'name'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Based on the answer ( https://stackoverflow.com/a/39822193/3443096 ) , here's another idea for mixin:
_.mixin({
extractLeaves: (obj, filter, subnode, subpathKey, rootPath, pathSeparator) => {
var filterKv = _(filter).toPairs().flatMap().value()
var arr = _.isArray(obj) ? obj : [obj]
return _.flatMap(arr, (v, k) => {
if (v[filterKv[0]] === filterKv[1]) {
var vClone = _.clone(v)
delete vClone[subnode]
vClone._absolutePath = rootPath + pathSeparator + vClone[subpathKey]
return vClone
} else {
var newRootPath = rootPath
if (_.isArray(obj)) {
newRootPath = rootPath + pathSeparator + v[subpathKey]
}
return _.extractLeaves(
v[subnode], filter, subnode,
subpathKey, newRootPath, pathSeparator
)
}
})
}
});
This work for this example JSON, where you want to extract leaf-nodes:
{
"name": "raka",
"type": "dir",
"children": [{
"name": "riki",
"type": "dir",
"children": [{
"name": "roko",
"type": "file"
}]
}]
}
Use it this way:
_.extractLeaves(result, {type: "file"}, "children", "name", "/myHome/raka", "/")
And you will get:
[
{
"name": "roko",
"type": "file",
"_absolutePath": "/myHome/raka/riki/roko"
}
]