I have a json, it is
{
"prop1.sub1.sub2": "content1",
"prop1.sub1.sub3": "content2",
"prop2.sub1.sub2": "content3",
"prop3.sub1.sub2": "content4"
}
I want to recovery the structure, like
{
"prop1": {
"sub1": {
"sub2" : "content1",
"sub3" : "content2"
}
},
"prop2": {
"sub1": {
"sub2" : "content3"
}
},
"prop3": {
"sub1": {
"sub2" : "content4"
}
}
}
I split the key with dot to get each key.
for (var key in json) {
var keySplit = key.split('.');
// Todo: recovery the structure
}
But not found a good solution.
Is anyone has solution?
You can use Array#reduce method.
var obj = {
"prop1.sub1.sub2": "content1",
"prop1.sub1.sub3": "content2",
"prop2.sub1.sub2": "content3",
"prop3.sub1.sub2": "content4"
};
// iterate over the property names
Object.keys(obj).forEach(function(k) {
// slip the property value based on `.`
var prop = k.split('.');
// get the last value fom array
var last = prop.pop();
// iterate over the remaining array value
// and define the object if not already defined
prop.reduce(function(o, key) {
// define the object if not defined and return
return o[key] = o[key] || {};
// set initial value as object
// and set the property value
}, obj)[last] = obj[k];
// delete the original property from object
delete obj[k];
});
console.log(obj);
Answer by Pranav C Balan is right for the question you asked. But JSON's might not be as simple as you have mentioned above and can have array's also and few keys might not have "." in them. To handle all these cases you can use the following one.
var obj = {
"prop1.sub1.sub2": "content1",
"prop1.sub1.sub3": "content2",
"prop2.sub1.sub2": "content3",
"prop3.0.sub2": "content4"
};
function createJSONStructure(obj) {
Object.keys(obj).forEach(function(k) {
var prop = k.split('.'); //split on . to get elements
if(prop.length >1){ //If there is no dot just key the value as is.
let data = obj;//Copy the default object to data in each loop
for(i=0;i<prop.length-1;i++){
if(data[prop[i]]){ // Check if the key exists
if((prop[i+1].match(/^\d+$/) && !Array.isArray(data[prop[i]])) // Check if the next key is a digit and the object we have is a JSON
|| (!prop[i+1].match(/^\d+$/) && Array.isArray(data[prop[i]]))){ // Check if the next key is not a digit and the object we have is a Array
throw new Error("Invalid header data"); //If any of the above cases satisfy we cannot add the key so we can throw an error.
}
data = data[prop[i]]; // If key exisits make the data variable as the value of the key
}else {
if(prop[i+1].match(/^\d+$/)){ //If the key is not available see if the next parameter is a digit or string to decide if its array or string
data[prop[i]] = [];
}else{
data[prop[i]] = {};
}
data = data[prop[i]]; //Assign this new object to data
}
};
data[prop[i]] = obj[k]; //Finally add the value to final key
delete obj[k]; // delete the exisiting key value
}
});
return obj;
}
console.log(createJSONStructure(obj));
Related
I have been using a version of the function below to create and/or add values to nested objects:
function assign(obj, keyPath, value) {
const lastKeyIndex = keyPath.length-1;
for (let i = 0; i < lastKeyIndex; ++ i) {
const key = keyPath[i];
if (!(key in obj)){
obj[key] = {}
}
obj = obj[key];
}
obj[keyPath[lastKeyIndex]] = value;
}
(Posted in 2011 by kennytm, slightly modified above:
Javascript: how to dynamically create nested objects using object names given by an array).
Example usage to specify a value in which the keys represent the (0) database
name, (1) table name, (3) id value, and (4) column name:
let obj = {}
assign(obj, ['farm', 'products', '25', 'product_name'], 'lettuce');
console.log(JSON.stringify(obj));
/* (reformatted)
{
"farm": {
"products": {
"25": {
"product_name":"lettuce"
}
}
}
}
*/
We can add a second value for the same row:
assign(obj, ['farm', 'products', '25', 'product_unit'], 'head');
console.log(JSON.stringify(obj));
/* (reformatted)
{
"farm": {
"products": {
"25": {
"product_name":"lettuce",
"product_unit":"head"
}
}
}
}
*/
Or additional values from different rows, tables, and databases:
assign(obj, ['farm', 'equipment', '17', 'equipment_name'], 'tractor');
console.log(JSON.stringify(obj));
/* (reformatted)
{
"farm": {
"products": {
"25": {
"product_name": "lettuce",
"product_unit": "head"
}
},
"equipment": {
"17": {
"equipment_name": "tractor"
}
}
}
}
*/
The function works perfectly but I can't figure out how it manages to aggregate the key path. It would appear to simply create or replace an existing object with an object consisting of only the last key and the value. In fact, if I execute the same statements not inside a function and without using a loop, the statements do exactly that.
(Starting with assignment of the first value to an empty object):
let obj = {}
let key;
// first iteration of the function's loop
key = 'farm';
if (!(key in obj)) {
obj[key] = {}
}
obj = obj[key];
// second iteration
key = 'products';
if (!(key in obj)) {
obj[key] = {}
}
obj = obj[key];
// third iteration
key = '25';
if (!(key in obj)) {
obj[key] = {}
}
obj = obj[key];
// final line from the function
obj['product name'] = 'lettuce';
console.log(JSON.stringify(obj));
// {"product name":"lettuce"}
As you can see, the object is not nested but simply replaced at each step.
What magic makes the function work differently?
The difference is that you didn't save a variable with a reference to the original object. When you reassign obj, you no longer have a variable containing the parent.
When using the function, that variable is in the function caller. Reassigning the local variable obj has no effect on the caller's variable.
To emulate this in your manual steps, change the beginning to:
const original = {};
let obj = original;
This is analogous to the way the function parameter is passed.
Then at the very end, do
console.log(JSON.stringify(original));
and you should see the whole object with all the nested properties added.
I have an object:
myObj = {
attendent-0-id:"123",
attendent-0-name:"Bob Smith",
attendent-1-id:"1234",
attendent-1-name:"Alex Smith",
attendent-2-id:"123",
attendent-2-name:"Charlie Smith",
attendent-maxGuest:1,
attendent-party-name:"",
}
I need to create a loop that go through myObj and find all the id's and then compares them for duplicates. So in this case it would log an error because attendent-0-id is equal to attendent-2-id.
If I do find duplicates I need to set a flag to true;
I have tried a bunch of things and am just stuck at this point. Thanks for any help.
In your case you can go through myObj using Object.keys() via:
for (const key of Object.keys(obj))
use a plain object as a map to store the previous values of the ids:
const map = {};
use a regex pattern to make sure only the specific ids are evaluated:
const pattern = /^attendent-\d+-id$/;
and then with the help of the map, log the error on duplicate ids:
if (value in map) {
console.error(`${map[value]} is equal to ${key}, which is ${value}`);
}
Example:
const myObj = {
'attendent-0-id': "123",
'attendent-0-name': "Bob Smith",
'attendent-1-id': "1234",
'attendent-1-name': "Alex Smith",
'attendent-2-id': "123",
'attendent-2-name': "Charlie Smith",
'attendent-maxGuest': 1,
'attendent-party-name': "",
};
function errorOnDuplicateIds(obj) {
const map = {};
const pattern = /^attendent-\d+-id$/;
for (const key of Object.keys(obj)) {
if (pattern.test(key)) {
const value = obj[key]
if (value in map) {
console.error(`${map[value]} is equal to ${key}, which is ${value}`);
} else {
map[value] = key
}
}
}
}
errorOnDuplicateIds(myObj);
const ids = []; // keep track of found ids
Object.keys(myObj).forEach(key => { // iterate over all properties of myObj
// check if property name is in format "attendent-" *some number* "-id"
if (/^attendent-\d+-id$/.test(key)) {
// check if the id has already been found
if (ids.findIndex(id => id === myObj[key]) !== -1) {
console.log('error');
} else {
ids.push(myObj[key]);
}
}
});
You can use Object.entries and a Map (keyed by value) for this:
var myObj = {"attendent-0-id":"123","attendent-0-name":"Bob Smith","attendent-1-id":"1234","attendent-1-name":"Alex Smith","attendent-2-id":"123","attendent-2-name":"Charlie Smith","attendent-maxGuest":1, "attendent-party-name":""};
var dupes = [...Object.entries(myObj).reduce(
(map, [key,val]) => map.set(val, (map.get(val) || []).concat(key)),
new Map
).values()].filter(keys => keys.length > 1);
console.log(dupes);
This solution does not give any particular meaning to the format of the keys.
Having said that, your object structure looks suspicious of bad design: you should not have enumerations in your object keys. For that you should use arrays.
Object.values(myObj) will create an array of all values and then you can use any way to find duplicate elements in that array.
var myValues = Object.values(myObj); //This will create an array of all values
var uniq = myValues.map((val) => {
return {count: 1, val: val}
}).reduce((a, b) => {
a[b.val] = (a[b.val] || 0) + b.count
return a
}, {});
var duplicates = Object.keys(uniq).filter((a) => uniq[a] > 1)
if (duplicates.length) {
return true;
} else {
return false;
}
My first advice would be to redefine your object to something more flexible.
let myObject = {
attendants : [
{
id: "123",
name: "Bob Smith"
},
{
id: "456",
name: "Alex Smith"
},
{
id: "768",
name: "Charlie Smith"
},
],
maxGuest: 1,
partyName: ""
};
This will allow you to iterate the attendants.
for (var attendant in myObject.attendants){
doSomething(attendant.id, attendant.name);
}
You can also sort the attendant:
// Sort by id
myObject.attendants.sort(function(left, right){
return left.value - right.value;
});
// Sort by name
myObject.attendants.sort(function (left, right){
var leftName = left.name.toLowerCase();
var rightName = right.name.toLowerCase();
if (leftName < rightName) return -1;
if (leftName > rightName) return 1;
return 0;
});
Now, lets assume you don't have a choice. Then it gets complicated.
You need to create (or modify an existent) a sort algorithm so it can use keys that are generated as:
myObject[`attendent-${index}-id`]
myObject[`attendent-${index}-name`]
and keep the pair
Consider the associative array below:
{
"body": [
{
"key1": "value1",
"body2": {
"key2": "value2"
}
}
]
}
How can I search the rough structure of this array? For example, how can I say is array[key1] lower than array[key2]? I apologize if this is not worded correctly.
Working fiddle: http://jsfiddle.net/4gy417o7/1/
objLevels = [];
function assignLevel(obj,level){
var tempArr = objLevels[level] || [];
for(var p in obj){
if(obj.hasOwnProperty(p))
tempArr.push(p);
if(typeof obj[p]=='object')
assignLevel(obj[p],(level+1));
objLevels[level] = tempArr;
}
}
assignLevel(yourObj,1);
console.log(objLevels);
By initiating the recursive function assignLevel with your original object and level=1, you should end up with an array of objects (objLevels), in which each object's key is the nesting level, and value is an array of the keys in your object that are at that nesting level.
So objLevels will be something like
[
{1: ["body"] },
{2: [0] }, // this is because body is an array, and the object inside it is at index 0
{3: ["key1","body1"] },
{4: ["key2] }
]
Then you can basically find any particular key variable's nesting level like so:
function getKeyLevel(key){
for(var level in objLevels)
if(objLevels[level].indexOf(key) > -1)
return level;
return false;
}
console.log(getKeyLevel("key2")) // 4
console.log(getKeyLevel("key1")) // 3
You should be able to do this using a recursive function, something like
function searchObject(object, level, parent){
// If the parent is omitted, we're at the start of the search.
var obj = object[parent] || object;
// For every key of the object supplied...
Object.keys(obj).forEach(
function(key){
alert(key+" found at level "+level+".");
// Check if object exists, then if it is of type Object
if(obj[key] !== undefined && obj[key].toString() === "[object Object]"){
// Search the object at a lower level
searchObject(obj[key], level+1, key);
}
}
);
}
Here's a fiddle showing it in action.
Have fun!
This question already has answers here:
How can I access and process nested objects, arrays, or JSON?
(31 answers)
Closed 9 years ago.
I'm new to javascript. Someone please show me how to store a map in local storage. Below is what I've tried. After storing I don't seem to be able to iterate the map keys.
UDATE2: IT MIGHT BE THE serializeObject function.
Why am I using this function? Otherwise when I post in AJAX, I get:
Uncaught TypeError: Converting circular structure to JSON
UPDATE: LOOKS LIKE THE PROBLEM IS BEFORE IT EVER GOES INTO LOCAL STORAGE.
var reportId = getGUID();
var theReports = localStorage.getItem('reports');
if (theReports == null) {
theReports = {};
}
theReports[reportId] = JSON.stringify($('#reportInfo').serializeObject());
// HERE ALSO I AM SEEING HUNDREDS OF FIELDS. I EXPECTED JUST reportID. I AM NOT SEEING THE KEY reportId. THIS IS BEFORE IT GOES INTO LOCAL STORAGE.
for (var prop in theReports)
{
console.log(prop);
}
localStorage.setItem('reports', JSON.stringify(theReports));
var tReports = localStorage.getItem('reports');
// This prints out 1,2,3,...500 for every field in #reportInfo form
// What I was expecting is reportId1, reportId2 etc. and definitely not the id of each field of the report itself!
for (var property in tReports) {
if (tReports.hasOwnProperty(property)) {
console.log(property);
}
}
Here is the serializeObject function. Perhaps this is the problem.
$(function() {
$.fn.serializeObject = function(){
var self = this,
json = {},
push_counters = {},
patterns = {
"validate": /^[a-zA-Z][a-zA-Z0-9_]*(?:\[(?:\d*|[a-zA-Z0-9_]+)\])*$/,
"key": /[a-zA-Z0-9_]+|(?=\[\])/g,
"push": /^$/,
"fixed": /^\d+$/,
"named": /^[a-zA-Z0-9_]+$/
};
this.build = function(base, key, value){
base[key] = value;
return base;
};
this.push_counter = function(key){
if(push_counters[key] === undefined){
push_counters[key] = 0;
}
return push_counters[key]++;
};
$.each($(this).serializeArray(), function(){
// skip invalid keys
if(!patterns.validate.test(this.name)){
return;
}
var k,
keys = this.name.match(patterns.key),
merge = this.value,
reverse_key = this.name;
while((k = keys.pop()) !== undefined){
// adjust reverse_key
reverse_key = reverse_key.replace(new RegExp("\\[" + k + "\\]$"), '');
// push
if(k.match(patterns.push)){
merge = self.build([], self.push_counter(reverse_key), merge);
}
// fixed
else if(k.match(patterns.fixed)){
merge = self.build([], k, merge);
}
// named
else if(k.match(patterns.named)){
merge = self.build({}, k, merge);
}
}
json = $.extend(true, json, merge);
});
return json;
};
The for..in loop gives you only the keys and not the items itself. You get the items with tReports[property]
var obj = {
"1": "one",
"2": "two",
"3": "three"
};
for (var key in obj) {
console.log(key); //1, 2, 3
console.log(obj[key]); //one, two, three
}
var tReports = localStorage.getItem('reports');
The variable tReports now holds the stringified version of the reports object.
The localStorage and sessionStorage only store a key/value pair as a string.
So if you wan't to store an object you need to serialize it to a string.
localStorage.setItem('key', JSON.stringify(myObject));
If you wan't to retrieve it from storage you need to deserialize it first before you can use it.
var myObject = JSON.parse(localStorage.getItem('key'));
https://developer.mozilla.org/en-US/docs/Web/Guide/DOM/Storage?redirectlocale=en-US&redirectslug=DOM%2FStorage
Instead of:
console.log(property);
Do:
console.log(tReports[property]);
Since the property variable is the actual property name, not its value.
Here's my json:
{"d":{"key1":"value1",
"key2":"value2"}}
Is there any way of accessing the keys and values (in javascript) in this array without knowing what the keys are?
The reason my json is structured like this is that the webmethod that I'm calling via jquery is returning a dictionary. If it's impossible to work with the above, what do I need to change about the way I'm returning the data?
Here's an outline of my webmethod:
<WebMethod()> _
Public Function Foo(ByVal Input As String) As Dictionary(Of String, String)
Dim Results As New Dictionary(Of String, String)
'code that does stuff
Results.Add(key,value)
Return Results
End Function
You can use the for..in construct to iterate through arbitrary properties of your object:
for (var key in obj.d) {
console.log("Key: " + key);
console.log("Value: " + obj.d[key]);
}
Is this what you're looking for?
var data;
for (var key in data) {
var value = data[key];
alert(key + ", " + value);
}
{
"d":{
"key1":"value1",
"key2":"value2"
}
}
To access first key write:
let firstKey=Object.keys(d)[0];
To access value of first key write:
let firstValue= d[firstKey];
By using word "b", You are still using key name.
var info = {
"fname": "Bhaumik",
"lname": "Mehta",
"Age": "34",
"favcolor": {"color1":"Gray", "color2":"Black", "color3":"Blue"}
};
Look at the below snippet.
for(key in info) {
var infoJSON = info[key];
console.log(infoJSON);
}
Result would be,
Bhaumik
Mehta
Object {color1: "Gray", color2: "Black", color3: "Blue"}
Don’t want that last line to show up? Try following code:
for(key in info) {
var infoJSON = info[key];
if(typeof infoJSON !== "object"){
console.log(infoJSON);
}
}
This will eliminate Object {color1: “Gray”, color2: “Black”, color3: “Blue”} from showing up in the console.
Now we need to iterate through the variable infoJSON to get array value. Look at the following whole peace of code.
for(key in info) {
var infoJSON = info[key];
if (typeof infoJSON !== "object"){
console.log(infoJSON);
}
}
for(key1 in infoJSON) {
if (infoJSON.hasOwnProperty(key1)) {
if(infoJSON[key1] instanceof Array) {
for(var i=0;i<infoJSON[key1].length;i++) {
console.log(infoJSON[key1][i]);
}
} else {console.log(infoJSON[key1]);}
}
}
And now we got the result as
Bhaumik
Mehta
Gray
Black
Blue
If we use key name or id then it’s very easy to get the values from the JSON object but here we are getting our values without using key name or id.
Use for loop to achieve the same.
var dlist = { country: [ 'ENGLAND' ], name: [ 'CROSBY' ] }
for(var key in dlist){
var keyjson = dlist[key];
console.log(keyjson)
}