I am converting JSON keys to the list with dot-notation. If any dot is there represent nested jsonobject and if any [](array notation) is there resents jsonarray.
var keyify = (obj, prefix = '') =>
Object.keys(obj).reduce((res, el) => {
if (Array.isArray(obj[el])) {
return [...res, ...keyify(obj[el][0], prefix + el + '[].')];
} else if (typeof obj[el] === 'object' && obj[el] !== null) {
return [...res, ...keyify(obj[el], prefix + el + '.')];
} else {
return [...res, prefix + el];
}
}, []);
Above is the sample code that I am using for the converion. If input is
{
"input": {
"test": {
"phone": [
{
"phone1": "123"
}
]
}
},
"customer": [
{
"lastname": "def",
"firstname": "abc"
}
]
}
Output will be:
[ 'input.test.phone[].phone1',
'customer[].lastname',
'customer[].firstname' ]
But the above code searches for only first JSONObject's keys in the JSONArray. But if the input is like this:
{
"input": {
"test": {
"phone": [
{
"phone1": "123"
},
{
"a": "456"
}
]
}
},
"customer": [
{
"lastname": "def",
"firstname": "abc"
}
]
}
Then in the above JSON case the code will give output :
[ 'input.test.phone[].phone1',
'customer[].lastname',
'customer[].firstname' ]
So, the key a is missing only phone1 is coming in the list.So, how to get if multiple json keys are there then get keys with index of first occurence.
Expected output
[ 'input.test.phone[0].phone1',
'input.test.phone[1].a',
'customer[0].lastname',
'customer[0].firstname' ]
And if the JSONarray is value then it should be replaced by empty string.
For input:
const data = {
"input": {
"test": {
"phone": [
{
"phone1": ["123456"]
},
{
"a": ["1","2","3","4"]
}
]
}
},
"customer": [
{
"lastname": "def",
"firstname": "abc"
}
]
}
In this case "phone1": ["123456"] and "a": ["1","2","3","4"] are Json array as values this case lis will be like:
Expected Output:
[ 'input.test.phone[0].phone1',//instead of 'input.test.phone[0].phone1[0]'
'input.test.phone[1].a',//instead of 'input.test.phone[1].a[0]','input.test.phone[1].a[1]','input.test.phone[1].a[2]','input.test.phone[1].a[3]',
'customer[0].lastname',
'customer[0].firstname' ]
In the above case jsonarray should be considered as value not key.
You could use for...in loop to create recursive function for this and check if the current data input is an array or not to add dot or square brackets.
const data = { "input": { "test": { "phone": [ { "phone1": ["123456"] }, { "a": ["1","2","3","4"] } ] } }, "customer": [ { "lastname": "def", "firstname": "abc" } ] }
function parse(data, prev = '') {
const result = []
const check = data => {
if (typeof data !== 'object') {
return false
}
if (Array.isArray(data)) {
if (data.some(e => (typeof e != 'object'))) {
return false
}
}
return true;
}
for (let i in data) {
let dot = prev ? '.' : ''
let str = Array.isArray(data) ? `[${i}]` : dot + i
let key = prev + str;
if (check(data[i])) {
result.push(...parse(data[i], key))
} else {
result.push(key)
}
}
return result
}
const result = parse(data);
console.log(result)
You can traverse through the scope of the object and capture any paths that have a non-object value.
This is an extremely uncoupled and generic soulution.
const traverse = (obj, visitorFn, scope = []) => {
for (let key in obj) {
visitorFn.apply(this, [key, obj[key], scope]);
if (obj[key] !== null && typeof obj[key] === 'object') {
traverse(obj[key], visitorFn, scope.concat(key));
}
}
}
const scopeToPath = (obj) => obj.reduce((path, key) =>
path + (!isNaN(key) ? `[${key}]` : `.${key}`), '').substring(1);
const findObjectPaths = (obj) => {
let paths = [];
traverse(obj, (key, value, scope) => {
if (typeof value !== 'object') {
paths.push(scopeToPath(scope.concat(key)));
}
});
return paths;
};
console.log(findObjectPaths(getData()));
function getData() {
return {
"input": {
"test": {
"phone": [{ "phone1": "123" }, { "a": "456" }]
}
},
"customer": [{ "lastname": "def", "firstname": "abc" }]
};
}
.as-console-wrapper { top: 0; max-height: 100% !important; }
You could take a nested approach by having a look to the types of the object.
function flat(object, keys = '') {
if (!object || typeof object !== 'object') return [keys];
if (Array.isArray(object))
return object.every(o => !o|| typeof o!== 'object')
? [keys]
: object.flatMap((o, i, { length }) =>
flat(o, `${keys}[${length === 1 ? '' : i}]`));
return Object
.entries(object)
.flatMap(([k, v]) => flat(v, `${keys}${keys && '.'}${k}`));
}
var data = { input: { test: { phone: [{ phone1: ["123456"] }, { a: ["1", "2", "3", "4"] }] } }, customer: [{ lastname: "def", firstname: "abc" }] },
result = flat(data);
console.log(result);
Related
I have input data like this:
[{
"name": "outField2",
"value": "something"
}, {
"name": "outField3[index].outField4",
"value": "something"
}, {
"name": "outField3[index].outField5",
"value": "something"
}, {
"name": "outField3[index].outField6.outField7",
"value": "something"
}]
I am trying to achieve an output like this based on substring '[index]' (i.e. if that substring is not present then that element should be an object instead of an array):
{
"outField2": "something",
"outField3[index]": [{
"outField4": "something",
"outField5": "something",
"outField6": {
"outField7": "something"
}
}]
}
My current code (below) is able to produce the outField3 as an object if there is no substring '[index]' but I'm unable to find a good solution to generate it as an array in the presence of the substring. Can someone help out? I've tried a few options but none gives me the desired result.
function doThis(item, index) {
let path = map[index].name.split(".");
if (path.length > 1) {
createNestedObject(mapOutput, path, map[index].value);
} else {
mapOutput[map[index].name] = map[index].value;
};
};
function createNestedObject(element, path, value) {
var lastElement = arguments.length === 3 ? path.pop() : false;
for (var i = 0; i < path.length; i++) {
if (path[i].includes('[index]')) {
/*some logic here to push the child elements
that do not contain [index] as an array into
the ones that contain [index]*/
} else {
element = element[path[i]] = element[path[i]] || {};
};
}
if (lastElement) element = element[lastElement] = value;
return element;
};
const map = [{
"name": "outField2",
"value": "something"
}, {
"name": "outField3[index].outField4",
"value": "something"
}, {
"name": "outField3[index].outField5",
"value": "something"
}, {
"name": "outField3[index].outField6.outField7",
"value": "something"
}];
let mapOutput = {};
map.forEach(doThis);
let mapOutputJSON = JSON.stringify(mapOutput, null, 2);
console.log(mapOutputJSON);
.as-console-wrapper { min-height: 100%!important; top: 0; }
you can do something like this
const data = [{
"name": "outField2",
"value": "something"
},
{
"name": "outField3[index].outField4",
"value": "something"
},
{
"name": "outField3[index].outField5",
"value": "something"
},
{
"name": "outField3[index].outField6.outField7",
"value": "something"
}
]
const buildObject = (paths, value, obj) => {
if (paths.length === 0) {
return value
}
const [path, ...rest] = paths
if(path.includes('[index]')) {
return {
...obj,
[path]: [buildObject(rest, value, (obj[path] || [])[0] || {})]
}
}
return {
...obj,
[path]: buildObject(rest, value, obj[path] || {})
}
}
const result = data.reduce((res, {
name,
value
}) => buildObject(name.split('.'), value, res), {})
console.log(result)
A possible generic approach which in my opinion also assigns the correct type of the OP's "outField3[index]" property (object type instead of an Array instance) is based on reduce where ...
the outer loop iterates the array of { name, value } items
by executing a single function accumulateObjectTypeFromPathAndValue where ...
this function does split each name-value into an array of object-path keys which then gets iterated by the inner reduce method where the passed object programmatically accumulates nested key-value pairs.
function accumulateObjectTypeFromPathAndValue(root, path, value) {
path
.split('.')
.reduce((obj, key, idx, arr) => {
if (!obj.hasOwnProperty(key)) {
Object.assign(obj, {
[ key ]: (idx === arr.length - 1)
? value
: {},
});
}
return obj[key];
}, root);
return root;
}
console.log(
[{
"name": "outField2",
"value": "something"
}, {
"name": "outField3[index].outField4",
"value": "something"
}, {
"name": "outField3[index].outField5",
"value": "something"
}, {
"name": "outField3[index].outField6.outField7",
"value": "something"
}].reduce((result, { name: path, value }) => {
return accumulateObjectTypeFromPathAndValue(result, path, value);
}, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
The above implementation of the 2nd reducer function then could be changed according to the OP's custom array-type requirements ...
function accumulateCustomObjectTypeFromPathAndValue(root, path, value) {
path
.split('.')
.reduce((obj, key, idx, arr) => {
if (!obj.hasOwnProperty(key)) {
Object.assign(obj, {
[ key ]: (idx === arr.length - 1)
? value
: {},
});
if (key.endsWith('[index]')) {
obj[ key ] = [obj[ key ]];
}
}
return Array.isArray(obj[ key ])
//? obj[ key ].at(-1) // last item.
? obj[ key ][obj[ key ].length - 1] // last item.
: obj[ key ];
}, root);
return root;
}
console.log(
[{
"name": "outField2",
"value": "something"
}, {
"name": "outField3[index].outField4",
"value": "something"
}, {
"name": "outField3[index].outField5",
"value": "something"
}, {
"name": "outField3[index].outField6.outField7",
"value": "something"
}].reduce((result, { name: path, value }) => {
return accumulateCustomObjectTypeFromPathAndValue(result, path, value);
}, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Request you to please help in building a function in javascript to obtain the mentioned output from the input given.
INPUT : An object (possibly a nested object)
example :
{
"message":"string" ,
"data1": {
"Output1": {
"leaf1": "abc",
"Leaf2": "123"
}
}
"data2": {
"Output2": {
"leaf3": "abc",
"leaf4": "123"
}
}
}
OUTPUT : An array of string
Example :
str= ["message", "data1.Output1.leaf1", "data1.Output1.leaf2" , "data2.Output2.leaf3","data2.Output2.leaf4"]
Something like this it will work
const getBranches = (data, prefix=[]) => {
if (typeof(data) !== 'object') {
return prefix.join('.')
}
return Object.entries(data).flatMap(([k, v]) => getBranches(v, [...prefix, k]))
}
const data = {
"message": "string",
"data1": {
"Output1": {
"leaf1": "abc",
"Leaf2": "123"
}
},
"data2": {
"Output2": {
"leaf3": "abc",
"leaf4": "123"
}
}
}
console.log(getBranches(data))
Second version
const data = {
"message": "string",
"data1": {
"Output1": {
"leaf1": [{
"b": {
"c": "12"
}
}]
}
},
"data2": {
"Output2": {
"leaf3": "abc",
"leaf4": "123"
}
}
}
const getBranches = (data, prefix = []) => {
if (typeof(data) !== 'object') {
return prefix.join('.')
}
return Object.entries(data).flatMap(([k, v]) => Array.isArray(data) ? getBranches(v, [...prefix]) : getBranches(v, [...prefix, k]))
}
console.log(getBranches(data))
Given the following object:
const ourObject = {
"payload": {
"streams": [
{
"children": {
"2165d20a-6276-468f-a02f-1abd65cad618": {
"additionalInformation": {
"narrative": {
"apple": "A",
"banana": "B"
},
"myInventory": {
"fruits": [
{
"name": "apple"
},
{
"name": "banana"
}
]
}
}
}
}
}
]
}
};
We're trying to find the path of myInventory, the issue is that the children's uuid will be different each time. Any idea how we can get the path to myInventory by providing it as a key and get the json path to it?
If things are dynamic, a programmatic key search could help
const ourObject = {
"payload": {
"streams": [
{
"children": {
"2165d20a-6276-468f-a02f-1abd65cad618": {
"additionalInformation": {
"narrative": {
"apple": "A",
"banana": "B"
},
"myInventory": {
"fruits": [
{
"name": "apple"
},
{
"name": "banana"
}
]
}
}
}
}
}
]
}
};
const getPath = (key, o) => {
if (!o || typeof o !== "object") {
return "";
}
const keys = Object.keys(o);
for(let i = 0; i < keys.length; i++) {
if (keys[i] === key ) {
return key;
}
const path = getPath(key, o[keys[i]]);
if (path) {
return keys[i] + "." + path;
}
}
return "";
};
const getValueForKey = (key, o) => {
if (!o || typeof o !== "object") {
return undefined;
}
const keys = Object.keys(o);
for(let i = 0; i < keys.length; i++) {
if (keys[i] === key ) {
return o[key];
}
const value = getValueForKey(key, o[keys[i]]);
if (value) {
return value;
}
}
return undefined;
}
console.log(getPath("myInventory", ourObject))
console.log(getValueForKey("myInventory", ourObject))
Not sure if I understand the question right but
let uuid = '2165d20a-6276-468f-a02f-1abd65cad618';
ourObject.payload.streams[0].children[uuid].additionalInformation.myInventory
var changingKey = Object.keys(ourObject["payload"]["streams"][0]["children"])[0];
console.log(ourObject["payload"]["streams"][0]["children"][changingKey]["additionalInformation"]["myInventory"]);
Okay, you could create a helper function that gets the UUID. Since it's an object, the lookup is close to O(1) especially given the case that the children has only one key-value pair here.
function getUUIDFromPayload(payload) {
let obj = payload.streams[0].children
let uuid = Object.keys(obj)[0]
return uuid
}
Usage
const uuid = getUUIDFromPayload(payload)
ourObject.payload.streams[0].children[uuid].additionalInformation.myInventory
I have valid JSON with this structure
const myJSONExample = {
"SubItems": [
{
"SubItems": [
{
"ItemNo": "000001"
}
],
"ItemNo": null,
"Number": null,
"price": 114.46
},
{
"SubItems": [
{
"Group": "0.4.004"
}
],
"type": null
},
{
"SubItems": [
{
"ItemNo": "000005"
},
{
"Quantity": 2
}
],
"Material": "Steel"
},
{
"Description": null
}
]
}
and just simply trying to format all number types in it, using recursive iteration.
const iterate = (obj) => {
Object.keys(obj).forEach(key => {
if(typeof(item[key]) == "number"){
item[key] = new Intl.NumberFormat("de-DE").format(item[key]) //format number for german lang.
}
if (typeof obj[key] === 'object') {
iterate(obj[key])
}
})
}
iterate(myJSONExample);
I used this functions on other JSONs, and been trying to understand for some time, why this throws TypeError: Cannot convert undefined or null to object
null is an "object" hence your issue. So add a truthy check
const iterate = (obj) => {
Object.keys(obj).forEach(key => {
const value = obj[key]
const valueType = typeof value
if (valueType === "number") {
obj[key] = new Intl.NumberFormat("de-DE").format(value)
} else if (valueType === 'object' && value) {
iterate(value)
}
})
}
I have an object like the following :
[
{
"uid": "aaa-aaa",
"name": "foo",
"children": []
},
{
"uid": "aaa-bbb",
"name": "bar",
"children": [
{
"uid": "aaa-bbc",
"name": "baz",
"children": []
},
{
"uid": "aaa-ccc",
"name": "fooz",
"children": [
{
"uid": "aaa-bcb",
"name": "Yeah !",
"children": []
}
]
}
]
}
]
I am trying to write a function that would take that object an uid as parameters and would return a path to the element with the uid in that object (or null if it's not found).
Something like this :
> getElementPath(bigObject, 'aaa-bcb')
[1, "children", 1, "children", 0]
or
> getElementPath(bigObject, 'aaa-bcb')
[1, 1, 0]
I know the function has to be recursive since there should be no limit in nesting levels. I have tried this but it always returns null :
function getElementPath (haystack, uid, currentPath = []) {
if (haystack.uid === uid) {
return currentPath
}
if (Array.isArray(haystack.children)) {
for (let i = 0; i < haystack.children.length; i++) {
let newPath = [...currentPath, i]
let path = getElementPath(haystack.children[i], uid, newPath)
if (path !== null) {
return path
}
}
}
return null
}
I'd use flat
Flatten the object and then loop over the Object keys until you find the one that has the appropriate value. Once you find it, the key is the path.
https://www.npmjs.com/package/flat
My (naive and quick) implementation would look like this. But what I don't love about it is that it knows to look at the "children" property, it's fine if you're data structure is well defined and doesn't change very often, the flat idea will work no matter if you change your data structure or not.
getPathForUid = (uid,obj,thisPath = []) => {
if(Array.isArray(obj)) {
return obj.reduce((acc,item,idx) => getPathForUid(uid,item,thisPath.concat(idx)),[]);
}
return obj.uid === uid ? thisPath : getPathForUid(uid,obj.children,thisPath.concat('children'));
}
Try this:
function getObject(listaJson, uid) {
var object = null,
param,
type = null;
if (listaJson.uid === uid) {
return listaJson;
}
for (param in listaJson) {
type = typeof(listaJson[param]);
if (type.toString().toLowerCase() === 'object') {
object = getObject(listaJson[param], uid);
}
if (object) {
return object;
}
}
return object;
}
console.log(getObject(json, 'aaa-aaa'));
console.log(getObject(json, 'aaa-bbb'));
console.log(getObject(json, 'aaa-bbc'));
console.log(getObject(json, 'aaa-ccc'));
console.log(getObject(json, 'aaa-bcb'));
console.log(getObject(json, 'aaa-xxx')); // null
console.log(getObject(json, 'yyy-jjj')); // null