I have form inputs that are generated based off an XML file.
We keep a reference of nested elements in the XML file via data-* attributes which can later be used to build an object. For example:
<parent>
<child>
<grandchild1>first</grandchild1>
<grandchild2>second</grandchild2>
</child>
</parent>
Becomes
<input type="text" data-nest="parent.child.grandchild1" value="first"/>
<input type="text" data-nest="parent.child.grandchild2" value="second"/>
When we submit the form, I create an object (with nested objects) based off of their data-nest attribute. The above would become
parent:{
child:{
grandchild1: first,
grandchild2: second
}
}
The issue that I am running into is where multiple of the same tag are found in the XML file, e.g.
<child>
<grandchild>first</grandchild>
<grandchildFriend>firstFriend</grandchildFriend>
</child>
<child>
<grandchild>second</grandchild>
<grandchildFriend>secondFriend</grandchildFriend>
</child>
When I create my object, I would like it so that if multiple occurrences of the same data-nest value are found, they are nested inside of an array in order to maintain the different values.
With the current setup, the second occurrence of the tag will understandably overwrite the first.
This is the final object structure I desire:
parent:{
child:[
{
grandchild: first,
grandchildFriend: firstFriend
},
{
grandchild: second,
grandchildFriend: secondFriend
}
]
}
TL;DR
I'd like to change an object to an array of nested objects if they have the same data-* attribute
Here's a fiddle of how the current code works to help give a better understanding.
replacing currentObj[lastKey] = value; with this:
if(typeof currentObj[lastKey] === 'undefined') {
currentObj[lastKey] = value;
} else {
if(typeof currentObj[lastKey] !== 'array')
currentObj[lastKey] = [currentObj[lastKey]];
currentObj[lastKey].push(value);
}
the code will do the following: if currentObj[lastKey] is not set, you will create an element as always, otherwise, if it is already set and is a string the code will convert it into an array and then push into the arrays the subsequent (as many as you want) elements
and the final result will be something similar to:
parent:{
...
grandchild:[
"first", "second"
]
...
}
Edit
in order to have a result in the format that you have requested, you need to do some more edit, like following:
var json = {};
config = {
set: function(keyValueString) {
var pair = keyValueString.split('=');
// left of the = is the key path
var keyPath = pair[0];
// right of the = is the value to set
var value = pair[1];
// split keyPath into an array of keys
var keys = keyPath.split('.');
var key;
var currentObj = json;
// loop through all keys in the key path, except the last one.
// this creates the object structure implied by the key path.
// we want to do something different on the last iteration.
for (var i=0; i < keys.length-1; i++) {
// Get the current key we are looping
key = keys[i];
// if the requested level on the current object doesn't exist,
// make a blank object.
if (typeof currentObj[key] === 'undefined') {
currentObj[key] = i === keys.length-2 ? []: {};
}
currentObj = currentObj[key];
}
// our loop doesn't handle the last key, because that's when we
// want to set the actual value.
var lastKey = keys[keys.length-1]
// set the property of the deepest object to the value.
var child = {};
child[lastKey] = value;
currentObj.push(child);
}
};
// iterate through all of our inputs, and nest them based off their data-* attribute
$('input').each(function(){
// set nest = value setup, e.g. parent.child.grandchild = first
// we then break this
var key = $(this).attr('data-nest') + '=' + $(this).val();
config.set(key);
});
// as a bonus - this is how I went about breaking multiple values within a single input into an array
function traverseObject(object){
// iterate through the object
for (var i in object){
// if the next key exists, run again
if(object[i] !== null && typeof(object[i])=="object"){
traverseObject(object[i]);
} else {
var value = [object[i]];
// if the value contains a comma
if(value.toString().indexOf(',') > -1){
// set the *nested* key to the
// value as an array by splitting at the commas
object[i] =value.toString().split(',').filter(function(el){
// remove the extra entry after splitting
return el.length != 0;
});
}
}
}
}
traverseObject(json);
$('#test').on('click', function(){
console.log(json)
});
Update your config.set() as follows:
var json = {};
config = {
set: function(keyValueString) {
var pair = keyValueString.split('=');
// left of the = is the key path
var keyPath = pair[0];
// right of the = is the value to set
var value = pair[1];
// split keyPath into an array of keys
var keys = keyPath.split('.');
var key;
var currentObj = json;
// loop through all keys in the key path, except the last one.
// this creates the object structure implied by the key path.
// we want to do something different on the last iteration.
for (var i=0; i < keys.length-1; i++) {
// Get the current key we are looping
key = keys[i];
// if the requested level on the current object doesn't exist,
// make a blank object.
if (typeof currentObj[key] === 'undefined') {
currentObj[key] = {};
}
currentObj = currentObj[key];
}
// our loop doesn't handle the last key, because that's when we
// want to set the actual value.
var lastKey = keys[keys.length-1]
if (typeof currentObj[lastKey] === 'undefined') {
// set string if nothing exists
currentObj[lastKey] = value;
} else if(!Array.isArray(currentObj[lastKey])) {
// if exists and is not an array,
// create an array with two items
// previously stored value (string) with lastKey key
// current value
var previous = { [lastKey]: currentObj[lastKey] };
var current = { [lastKey]: value };
currentObj[lastKey] = [previous, current];
} else if(Array.isArray(currentObj[key])){
// if exists and is an array
// push the new object to it
var current = { [lastKey]: value }
currentObj[lastKey].push(current);
}
}
};
// iterate through all of our inputs, and nest them based off their data-* attribute
$('input').each(function(){
// set nest = value setup, e.g. parent.child.grandchild = first
// we then break this
var key = $(this).attr('data-nest') + '=' + $(this).val();
config.set(key);
});
// as a bonus - this is how I went about breaking multiple values within a single input into an array
function traverseObject(object){
// iterate through the object
for (var i in object){
var value = [object[i]];
// EDIT: continue only if value is not an array
if(!Array.isArray(object[i])) {
// if the value contains a comma
if(value.toString().indexOf(',') > -1){
// set the *nested* key to the
// value as an array by splitting at the commas
object[i] = value.toString().split(',').filter(function(el){
// remove the extra entry after splitting
return el.length != 0;
});
}
// if the next key exists, run again
if(object[i] !== null && typeof(object[i])=="object"){
traverseObject(object[i]);
}
}
}
}
traverseObject(json);
$('#test').on('click', function(){
console.log(json)
});
#test{
padding: 5px;
border: 1px solid black;
font-size: 15px;
cursor: pointer;
width: 150px;
margin: 5px;
text-align: center;
}
input{
display: block;
margin: 5px;
padding: 5px;
width: 150px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<label>Below are unique tags (working fine)</label>
<input type="text" data-nest="parent.child.grandchild1" value="gchild1-first"/>
<input type="text" data-nest="parent.child.grandchild2" value="gchild2-second"/>
<label>Below is multiple occurances of the same tag (find a way to break these into an array of objects)</label>
<input type="text" data-nest="parent.child.grandchild" value="gchild-first"/>
<input type="text" data-nest="parent.child.grandchild" value="gchild-second"/>
<label>Single array example</label>
<input type="text" data-nest="parent.child.array" value="this,is,an,array"/>
<div id="test">
Test
</div>
Try this https://jsfiddle.net/ado7eLL9/19/
by updating the set method of config like this
if (typeof currentObj[lastKey] === 'undefined') {
currentObj[lastKey] = value;
} else if(!Array.isArray(currentObj[lastKey])) {
currentObj[lastKey] = [currentObj[lastKey], value];
} else if(Array.isArray(currentObj[key])){
currentObj[lastKey].push(value);
}
This problem has been solved by attacking with a different approach.
We set the element we want as the array with [] added to the end of its data-nest attribute - we can then use this as the baseline for what we want converted.
If anyone is curious, here's the updated fiddle
Related
I have some JSON data that I am retrieving from https://status.mojang.com/check and am storing in a variable. I'm still quite new to JSON/JS and I can't seem to find any answers on google.
Code:
function checkMojang() {
var mojangStatus = mojang.status();
mojangStatus.then(function (message) {
var response = JSON.parse(message);
})
}
Data I am using can be seen at the link above. I am trying to check all the data in the json array, see if any of the values contain "yellow" or "red" and get the keys for those values along with their checked value but can't figure out how to do so.
You can loop through the array and then through the object properties and make a new object using the colors as keys
var response = [{"minecraft.net":"green"},{"session.minecraft.net":"red"},{"account.mojang.com":"green"},{"auth.mojang.com":"green"},{"skins.minecraft.net":"green"},{"authserver.mojang.com":"yellow"},{"sessionserver.mojang.com":"green"},{"api.mojang.com":"green"},{"textures.minecraft.net":"green"},{"mojang.com":"red"}];
var new_response = {};
response.forEach(function(obj){
for (var prop in obj) {
if(obj.hasOwnProperty(prop)) {
if(new_response[obj[prop]] == undefined) new_response[obj[prop]] = [];
new_response[obj[prop]].push(prop);
}
}
})
console.log(new_response);
The you can use the object for your needs as
new_response["red"]
giving you the list of all key with red value.
you can use the method array.foreach() to execute a provided function once per array element and the for ... in to itarate over the enumarable properties.
So you can test the value and get keys for the value "yellow" or "red"
response.forEach(function(element) {
for (k in element) {
if (element[k]=="red" or element[k]=="yellow") {
// k is the key
}
}
});
function checkMojang() {
var mojangStatus = mojang.status();
mojangStatus.then(function (message) {
var response = JSON.parse(message);
for (i = 0; i < response.length; i++) { // iterate over response array
var item = response[i]; // get item from array
var key = Object.keys(item)[0]; // get the key of the item
var value = item[key]; // get the value of the item
if (value === 'yellow' || value === 'red') {
// do something, like adding it to a list
}
}
});
}
Im using the following code,
jQuery.each(aDataSel, function(index, oData) {
oPushedObject = {};
aSelectedDataSet.push(fnCreateEnt(aProp, oData, oPushedObject));
});
This is aSelectedDataSet values
and this is the values of OData
What I need is that before I do the push is to fill the listTypeGroup & listTypeGroupDescription (with the red arrow ) with values that Are inside the oData -> ListTypeGroupAssigment -> result (listTypeGroup & listTypeGroupDescription) , The index is relevant since I want to add just the value of the index in each iteration (since this code is called inside outer loop and the index determine the current step of the loop) ,How it can be done nicely?
The result contain 100 entries (always) and the a selected data will have 100 entries at the end...
Update :)
Just to be clear In the pic I show the values which is hardcoded for this run but the values can be any values, we just need to find the match between the both objects values...
I mean to find a match between to_ListTypeGroupAssigment in both object (which in this case exist ) and if in oData there is result bigger then one entry start with the matching ...
UPDATE2 - when I try Dave code the following happen for each entry,
This happen in the Jquery.extend line...any idea how to overcome this?
The following hard-coded of Dave:-) work perfect but I need generic code which doesnt refer to specific field name
jQuery.each(aDataSet, function(index, oData) {
oPushedObject = {};
fnCreatePushedEntry(aProperties, oData, oPushedObject);
var result = oData.to_ListTypeGroupAssignment.results[index];
oPushedObject.to_ListTypeGroupAssignment = {
ListTypeGroup: result.ListTypeGroup,
ListTypeGroupDescription: result.ListTypeGroupDescription
};
aSelectedDataSet.push(oPushedObject);
});
Im stuck :(any idea how to proceed here ?what can be wrong with the extend ?
should I use something else ? Im new to jQuery...:)
I think that this happen(in Dave answer) because the oData[key] is contain the results and not the specified key (the keyValue = to_ListTypeGroupAssignment ) which is correct but we need the value inside the object result per index...
var needValuesForMatch = {
ListTypeGroup: 'undefined',
ListTypeGroupDescription: 'undefined',
}
//Just to show that oPushedObject can contain additional values just for simulation
var temp = {
test: 1
};
//------------------This object to_ListTypeGroupAssigment should be filled (in generic way :) ------
var oPushedObject = {
temp: temp,
to_ListTypeGroupAssignment: needValuesForMatch
};
oPushedObject is one instance in aSelectedDataSet
and after the matching I need to do the follwing:
aSelectedDataSet.push(oPushedObject);
Is this what you're after:
OPTION ONE - DEEP CLONE FROM oData TO aSelectedDataSet
aSelectedDataSet.forEach(function(currentObject,index){
for (var childObject in currentObject) {
if (! currentObject.hasOwnProperty(childObject))
continue;
var objectToClone = oData[childObject]['results'][index];
if(objectToClone)
$.extend(true,currentObject[childObject],objectToClone);
}
});
Here is your data in a fiddle with the function applied: https://jsfiddle.net/hyz0s5fe/
OPTION TWO - DEEP CLONE FROM oData ONLY WHERE PROPERTY EXISTS IN aSelectedDataSet
aSelectedDataSet.forEach(function(currentObject,index){
for (var childObject in currentObject) {
if (! currentObject.hasOwnProperty(childObject))
continue;
if(typeof currentObject[childObject] !== 'object')
continue;
for(var grandChildObject in currentObject[childObject]) {
var objectToClone = oData[childObject]['results'][index][grandChildObject];
if(typeof objectToClone === 'object') {
$.extend(true,currentObject[childObject][grandChildObject],objectToClone);
} else {
currentObject[childObject][grandChildObject] = objectToClone;
}
}
}
Fiddle for option 2: https://jsfiddle.net/4rh6tt25/
If I am understanding you correctly this should just be a small change:
jQuery.each(aDataSel, function(index, oData) {
oPushedObject = {};
fnCreateEnt(aProp, oData, oPushObj);
//get all the properties of oData and clone into matching properties of oPushObj
Object.getOwnPropertyNames(oData).forEach(function(key) {
if (oPushObj.hasOwnProperty(key)) {
//oPushObj has a matching property, start creating destination object
oPushObj[key] = {};
var source = oData[key];
var destination = oPushObj[key];
//can safely assume we are copying an object. iterate through source properties
Object.getOwnPropertyNames(source).forEach(function(sourceKey) {
var sourceItem = source[sourceKey];
//handle property differently for arrays
if (Array.isArray(sourceItem)) {
//just copy the array item from the appropriate index
destination[sourceKey] = sourceItem.slice(index, index + 1);
} else {
//use jQuery to make a full clone of sourceItem
destination[sourceKey] = $.extend(true, {}, sourceItem);
}
});
}
});
aSelectedDataSet.push(oPushedObject);
});
It is unclear what exactly your fnCreateEnt() function returns though. I am assuming it is the populated oPushObj but it's not entirely clear from your question.
I have an object with numbers as keys and image paths as values. I am getting numbers from a combination of selected radio buttons. Such as 1111 or 2111 if the first radio buttons in each group are all selected or the second radio then all first. I want to search the object for a key of 1111 then if it exists, return its value, which would be an image path. I can successfully find if the object has a matching key, but how do I return the value for that key only? In the case of 1111 I would need to return "my/image/path1". Here is what I have so far:
var array = [];
var imgs = {
1111: "my/image/path1",
2111: "my/image/path2",
1211: "my/image/path3",
1311: "my/image/path4"
}
$(':radio').change(function() {
$(":radio:checked").each(function(i, e) {
array[i] = $(this).val();
});
var total = 0;
$.each(array,function() {
total += this;
});
matchKey = parseInt(total, 10);
// here is where I'm stuck
if (imgs contains the key matchKey)) {
console.log(value for matchKey);
}
});
You can use square-bracket notation
if (imgs[matchKey]) {
console.log(imgs[matchKey]);
}
Note: This assumes none of your values will ever be falsey (eg, 0, a blank string, false etc). Which I think is fine, as you said your values are always non-empty paths. But the warning stands. If your values could legitimately be falsy, check #Florian answer.
In your case you can use plain javascript:
if (typeof imgs[matchKey] !== "undefined") { // Check if the key exists
var value = imgs[matchKey];
}
You can use Object.keys(imgs) to retrieve an array of just the keys, and then perform a simple test to see if the key you're looking for is contained in the array:
if (Object.keys(imgs).indexOf('1111') > -1)
You can get the key-value pair by iterating through each element in array imgs using $.each
var imgs = {
1111: "my/image/path1",
2111: "my/image/path2",
1211: "my/image/path3",
1311: "my/image/path4"
}
$.each(imgs,function(key,value)
{
if(key === 1111)
return value; // This would give you the path "my/image/path1"
});
I am getting a random key value pair,Can i assign it to an array?
Its problematic here when I assign it like arr[50] = 'abc' it automatically creates the keys upto 50 like arr[0],arr[1],arr[2] and so on.
and i wanted an array like this arr[50=>'abc','40'=>'pqr','53'=>'lmn']
I have it here
if(typeof(feedArr.latestRating) == 'object'){
jQuery.each(feedArr.latestRating,function(key,val){alert('key::'+key);
if(key in newRatingArr){
//delete the key if already exists
newRatingArr.splice(key,1);
}else{
//insert the key,value
newRatingArr[key] = val; //here is the problem occurs when key is 50 it automatically creates the indexes in the array upto 50 which i dont want
// alert('Key between::'+key);
// alert('Value between::'+newRatingArr[key]);
//newRatingArr.splice(key,0,val);
}
//alert(key);
emptyRate = 0;
});
}else{
emptyRate = 1;
}
What can i do here?Please let me know.
Use an object {} instead of an array [].
Objects can act as unordered key-value containers, which is what you seem to be needing here.
// somewhere...
var newRatingArr = {};
// your code.
var emptyRate = true;
if (typeof (feedArr.latestRating) == 'object') {
jQuery.each(feedArr.latestRating, function (key, val) {
newRatingArr[key] = val;
emptyRate = false;
});
}
Given the following HTML form:
<form id="myform">
Company: <input type="text" name="Company" value="ACME, INC."/>
First Name: <input type="text" name="Contact.FirstName" value="Daffy"/>
Last Name: <input type="text" name="Contact.LastName" value="Duck"/>
</form>
What is the best way serialize this form in javascript to a JSON object in the format:
{
Company:"ACME, INC.",
Contact:{FirstName:"Daffy", LastName:"Duck"}
}
Also note that there might be more than 1 "." sign in the field name.
I think that what you'd do is this: for each input, first split the name at the separators (the '.' characters). Now, you have an array of names. You can then iterate through that array, making sure that your target "assembly" object (and sub-objects) have containers every time you come across a new name segment. When the array has 1 element in it, you simply add the value.
$.fn.extractObject = function() {
var accum = {};
function add(accum, namev, value) {
if (namev.length == 1)
accum[namev[0]] = value;
else {
if (accum[namev[0]] == null)
accum[namev[0]] = {};
add(accum[namev[0]], namev.slice(1), value);
}
};
this.find('input, textarea, select').each(function() {
add(accum, $(this).attr('name').split('.'), $(this).val());
});
return accum;
});
// ...
var object = $('#myform').extractObject();
I just sort-of made that up so there might be a bug or two; I can't remember whether all the browsers have "slice" but I think they do.
(edit: I forgot the all-important call to split())
You can loop through the form fields by name, use String#split to split the names on dot, and build up your resulting structure. Concept code:
function serializeDeep(form) {
var rv, obj, elements, element, index, names, nameIndex, value;
rv = {};
elements = form.elements;
for (index = 0; index < elements.length; ++index) {
element = elements[index];
name = element.name;
if (name) {
value = $(element).val();
names = name.split(".");
obj = rv;
for (nameIndex = 0; nameIndex < names.length; ++nameIndex) {
name = names[nameIndex];
if (nameIndex == names.length - 1) {
obj[name] = value;
}
else {
obj = obj[name] = obj[name] || {};
}
}
}
}
return rv;
}
Note that that doesn't allow for fields with repeated names (which should create arrays), nor does it elegantly handle a situation where you use the names "foo" and "foo.bar". But it should get you started.
I have managed it this way:
$('#Myform').attr('onsubmit', 'test()');
function test() {
var obj = {};
obj.title =$('#title').prop('value');
console.log('title: '+obj.title);
obj.website =$('#website').prop('value');
console.log('website: '+obj.website);
obj.tags =$('#tags').prop('value').split(',');
console.log('tags: '+obj.tags);
do_something(JSON.stringify(obj));
}
Of course this can be done if you know what the names are, and I am in fact generating the table itself using Formation plug-in.
I created an example for this question by using plain js, please check developer tool console to see the data object!
jsfiddle example
var data = {};
var array = 'person.name.first'.split('.');
var value = 'myFirstName';
generateObj(data, array, value);
console.log(data);
function generateObj(obj, arr, val) {
if (arr.length === 1) {
obj[arr[0]] = val
return;
}
var restArr = arr.splice(1);
if (!obj[arr[0]]) {
obj[arr[0]] = {};
}
generateObj(obj[arr[0]], restArr, val);
}
solution:
transform each name string to array.
iterate through each array.
recursively call a method which create an obj and set this obj as the value of the property and pass this obj to the next recursion.
Create an object of that shape then use a JSON encoder to write it out.