XML To JSON Conversion with attributes - javascript

I am trying to convert the XML to JSON.Here am facing challenge my xml have #attributes name as "value" in all tag. while convert into xml to JSON i am using the below code.
var xml = "<Message><id value="123"></id><type value="Test"></type></Message>"
var json = XMLtoJSON(xml, ["type", "space", "xmlns", "html"]);
var result = JSON.stringify(json)
function XMLtoJSON(xml, ignored) {
var r, children = xml.*, attributes = xml.#*, length = children.length();
if(length == 0) {
r = xml.toString();
} else if(length == 1) {
var text = xml.text().toString();
if(text) {
r = text;
}
}
if(r == undefined) {
r = {};
for each (var child in children) {
var name = child.localName();
var json = XMLtoJSON(child, ignored);
var value = r[name];
if(value) {
if(value.length) {
value.push(json);
} else {
r[name] = [value, json]
}
} else {
r[name] = json;
}
}
}
if(attributes.length()) {
var a = {}, c = 0;
for each (var attribute in attributes) {
var name = attribute.localName();
if(ignored && ignored.indexOf(name) == -1) {
a["_" + name] = attribute.toString();
c ++;
}
}
if(c) {
if(r) a._ = r;
return a;
}
}
return r;
}
Input XML :
<Message><id value="123"></id><type value="Test"></type></Message>
Actual Output:
{"id":{"_value":"123"},"type":{"_value":"Test"}}
Expected Output:
{"id":"123","type":"Test"}
Guide me where am missing the part to get the expected output.
Regards,
nkn1189

do you think if you do this way will work for you?
put your actual output from that parser to this function:
function convertToExpectedOutput(obj){
var result = {}
for (var i in obj){
if (i == "_value")
return obj[i];
else
result[i] = convertToExpectedOutput(obj[i])
}
return result;
}
convertToExpectedOutput(actualOutput)
So, for your array, hange the convertToExpectedOutput to this way and it will give the expected result:
function convertToExpectedOutput(obj){
var result = {}
for (var i in obj){
if (i == "_value")
return obj[i];
else
if (Array.isArray(obj[i])){
result[i] = [];
arr = obj[i]
for (var j in arr)
result[i].push(convertToExpectedOutput(arr[j]))
}
else
result[i] = convertToExpectedOutput(obj[i])
}
return result;
}

Related

Get a value of a HashTable

I was making a HashTable to have as an example and have it saved for any problem, but I ran into a problem trying to implement a method that returns true or false in case the value belongs to the HashTable, since it is inside a arrays of objects as comment in the code.
I have tried for loops, .map and for of, but it always fails, if someone could help me.
function HashTable () {
this.buckets = [];
this.numbuckets = 35;
}
HashTable.prototype.hash = function (key) {
let suma = 0;
for (let i = 0; i < key.length; i++) {
suma = suma + key.charCodeAt(i);
}
return suma % this.numbuckets;
}
HashTable.prototype.set = function (key, value) {
if (typeof key !== "string") {
throw new TypeError ("Keys must be strings")
} else {
var index = this.hash(key);
if(this.buckets[index] === undefined) {
this.buckets[index] = {};
}
this.buckets[index][key] = value;
}
}
HashTable.prototype.get = function (key) {
var index = this.hash(key);
return this.buckets[index][key];
}
HashTable.prototype.hasKey = function (key) {
var index = this.hash(key);
return this.buckets[index].hasOwnProperty(key)
}
HashTable.prototype.remove = function (key) {
var index = this.hash(key);
if (this.buckets[index].hasOwnProperty(key)) {
delete this.buckets[index]
return true;
}
return false;
}
HashTable.prototype.hasValue = function (value) {
let result = this.buckets;
result = result.flat(Infinity);
return result // [{Name: Toni}, {Mame: Tino}, {Answer: Jhon}]
}
You can use Object.values() to get the values of all the propertyies in the bucket.
function HashTable() {
this.buckets = [];
this.numbuckets = 35;
}
HashTable.prototype.hash = function(key) {
let suma = 0;
for (let i = 0; i < key.length; i++) {
suma = suma + key.charCodeAt(i);
}
return suma % this.numbuckets;
}
HashTable.prototype.set = function(key, value) {
if (typeof key !== "string") {
throw new TypeError("Keys must be strings")
} else {
var index = this.hash(key);
if (this.buckets[index] === undefined) {
this.buckets[index] = {};
}
this.buckets[index][key] = value;
}
}
HashTable.prototype.get = function(key) {
var index = this.hash(key);
return this.buckets[index][key];
}
HashTable.prototype.hasKey = function(key) {
var index = this.hash(key);
return this.buckets[index].hasOwnProperty(key)
}
HashTable.prototype.remove = function(key) {
var index = this.hash(key);
if (this.buckets[index].hasOwnProperty(key)) {
delete this.buckets[index]
return true;
}
return false;
}
HashTable.prototype.hasValue = function(value) {
return this.buckets.some(bucket => Object.values(bucket).includes(value));
}
let h = new HashTable;
h.set("Abc", 1);
h.set("Def", 2);
console.log(h.hasValue(1));
console.log(h.hasValue(3));

Javascript: Determine unknown array length and map dynamically

Going to do my best at explaining what I am trying to do.
I have two models, mine and an api response I am receiving. When the items api response comes in, I need to map it to my model and inserts all the items. This is simple of course. Heres the issue, I need to do so without really knowing what I am dealing with. My code will be passed in two strings, one of my models mapping path and one of the api response mapping path.
Here are the two paths
var myPath = "outputModel.items[].uniqueName"
var apiPath = "items[].name"
Basically FOR all items in apiPath, push into items in myPath and set to uniqueName
What it comes down to is that my code has NO idea when two items need to be mapped, or even if they contain an array or simple field to field paths. They could even contain multiple arrays, like this:
******************** EXAMPLE *************************
var items = [
{
name: "Hammer",
skus:[
{num:"12345qwert"}
]
},
{
name: "Bike",
skus:[
{num:"asdfghhj"},
{num:"zxcvbn"}
]
},
{
name: "Fork",
skus:[
{num:"0987dfgh"}
]
}
]
var outputModel = {
storeName: "",
items: [
{
name: "",
sku:""
}
]
};
outputModel.items[].name = items[].name;
outputModel.items[].sku = items[].skus[].num;
************************ Here is the expected result of above
var result = {
storeName: "",
items: [
{
name: "Hammer",
sku:"12345qwert"
},
{
name: "Bike",
sku:"asdfghhj"
},
{
name: "Bike",
sku:"zxcvbn"
},
{
name: "Fork",
sku:"0987dfgh" }
]
};
I will be given a set of paths for EACH value to be mapped. In the case above, I was handed two sets of paths because I am mapping two values. It would have to traverse both sets of arrays to create the single array in my model.
Question - How can I dynamically detect arrays and move the data around properly no matter what the two model paths look like? Possible?
So you have defined a little language to define some data addressing and manipulation rules. Let's think about an approach which will allow you to say
access(apiPath, function(value) { insert(myPath, value); }
The access function finds all the required items in apiPath, then calls back to insert, which inserts them into myPath. Our job is to write functions which create the access and insert functions; or, you could say, "compile" your little language into functions we can execute.
We will write "compilers" called make_accessor and make_inserter, as follows:
function make_accessor(program) {
return function(obj, callback) {
return function do_segment(obj, segments) {
var start = segments.shift() // Get first segment
var pieces = start.match(/(\w+)(\[\])?/); // Get name and [] pieces
var property = pieces[1];
var isArray = pieces[2]; // [] on end
obj = obj[property]; // drill down
if (!segments.length) { // last segment; callback
if (isArray) {
return obj.forEach(callback);
} else {
return callback(obj);
}
} else { // more segments; recurse
if (isArray) { // array--loop over elts
obj.forEach(function(elt) { do_segment(elt, segments.slice()); });
} else {
do_segment(obj, segments.slice()); // scalar--continue
}
}
}(obj, program.split('.'));
};
}
We can now make an accessor by calling make_accessor('items[].name').
Next, let's write the inserter:
function make_inserter(program) {
return function(obj, value) {
return function do_segment(obj, segments) {
var start = segments.shift() // Get first segment
var pieces = start.match(/(\w+)(\[\])?/); // Get name and [] pieces
var property = pieces[1];
var isArray = pieces[2]; // [] on end
if (segments.length) { // more segments
if (!obj[property]) {
obj[property] = isArray ? [] : {};
}
do_segment(obj, segments.slice());
} else { // last segment
obj[property] = value;
}
}(obj, program.split('.'));
};
}
Now, you can express your whole logic as
access = make_accessor('items[].name');
insert = make_inserter('outputModel.items[].uniqueName');
access(apiPath, function(val) { insert(myPath, val); });
As mentioned in the comments, there is no strict definition of the input format, it is hard to do it with perfect error handling and handle all corner cases.
Here is my lengthy implementation that works on your sample, but might fail for some other cases:
function merge_objects(a, b) {
var c = {}, attr;
for (attr in a) { c[attr] = a[attr]; }
for (attr in b) { c[attr] = b[attr]; }
return c;
}
var id = {
inner: null,
name: "id",
repr: "id",
type: "map",
exec: function (input) { return input; }
};
// set output field
function f(outp, mapper) {
mapper = typeof mapper !== "undefined" ? mapper : id;
var repr = "f("+outp+","+mapper.repr+")";
var name = "f("+outp;
return {
inner: mapper,
name: name,
repr: repr,
type: "map",
clone: function(mapper) { return f(outp, mapper); },
exec:
function (input) {
var out = {};
out[outp] = mapper.exec(input);
return out;
}
};
}
// set input field
function p(inp, mapper) {
var repr = "p("+inp+","+mapper.repr+")";
var name = "p("+inp;
return {
inner: mapper,
name: name,
repr: repr,
type: mapper.type,
clone: function(mapper) { return p(inp, mapper); },
exec: function (input) {
return mapper.exec(input[inp]);
}
};
}
// process array
function arr(mapper) {
var repr = "arr("+mapper.repr+")";
return {
inner: mapper,
name: "arr",
repr: repr,
type: mapper.type,
clone: function(mapper) { return arr(mapper); },
exec: function (input) {
var out = [];
for (var i=0; i<input.length; i++) {
out.push(mapper.exec(input[i]));
}
return out;
}
};
}
function combine(m1, m2) {
var type = (m1.type == "flatmap" || m2.type == "flatmap") ? "flatmap" : "map";
var repr = "combine("+m1.repr+","+m2.repr+")";
return {
inner: null,
repr: repr,
type: type,
name: "combine",
exec:
function (input) {
var out1 = m1.exec(input);
var out2 = m2.exec(input);
var out, i, j;
if (m1.type == "flatmap" && m2.type == "flatmap") {
out = [];
for (i=0; i<out1.length; i++) {
for (j=0; j<out2.length; j++) {
out.push(merge_objects(out1[i], out2[j]));
}
}
return out;
}
if (m1.type == "flatmap" && m2.type != "flatmap") {
out = [];
for (i=0; i<out1.length; i++) {
out.push(merge_objects(out1[i], out2));
}
return out;
}
if (m1.type != "flatmap" && m2.type == "flatmap") {
out = [];
for (i=0; i<out2.length; i++) {
out.push(merge_objects(out2[i], out1));
}
return out;
}
return merge_objects(out1, out2);
}
};
}
function flatmap(mapper) {
var repr = "flatmap("+mapper.repr+")";
return {
inner: mapper,
repr: repr,
type: "flatmap",
name: "flatmap",
clone: function(mapper) { return flatmap(mapper); },
exec:
function (input) {
var out = [];
for (var i=0; i<input.length; i++) {
out.push(mapper.exec(input[i]));
}
return out;
}
};
}
function split(s, t) {
var i = s.indexOf(t);
if (i == -1) return null;
else {
return [s.slice(0, i), s.slice(i+2, s.length)];
}
}
function compile_one(inr, outr) {
inr = (inr.charAt(0) == ".") ? inr.slice(1, inr.length) : inr;
outr = (outr.charAt(0) == ".") ? outr.slice(1, outr.length) : outr;
var box = split(inr, "[]");
var box2 = split(outr, "[]");
var m, ps, fs, i, j;
if (box == null && box2 == null) { // no array!
m = id;
ps = inr.split(".");
fs = outr.split(".");
for (i=0; i<fs.length; i++) { m = f(fs[i], m); }
for (j=0; j<ps.length; j++) { m = p(ps[j], m); }
return m;
}
if (box != null && box2 != null) { // array on both sides
m = arr(compile_one(box[1], box2[1]));
ps = box[0].split(".");
fs = box[0].split(".");
for (i=0; i<fs.length; i++) { m = f(fs[i], m); }
for (j=0; j<ps.length; j++) { m = p(ps[j], m); }
return m;
}
if (box != null && box2 == null) { // flatmap
m = flatmap(compile_one(box[1], outr));
ps = box[0].split(".");
for (j=0; j<ps.length; j++) { m = p(ps[j], m); }
return m;
}
return null;
}
function merge_rules(m1, m2) {
if (m1 == null) return m2;
if (m2 == null) return m1;
if (m1.name == m2.name && m1.inner != null) {
return m1.clone(merge_rules(m1.inner, m2.inner));
} else {
return combine(m1, m2);
}
}
var input = {
store: "myStore",
items: [
{name: "Hammer", skus:[{num:"12345qwert"}]},
{name: "Bike", skus:[{num:"asdfghhj"}, {num:"zxcvbn"}]},
{name: "Fork", skus:[{num:"0987dfgh"}]}
]
};
var m1 = compile_one("items[].name", "items[].name");
var m2 = compile_one("items[].skus[].num", "items[].sku");
var m3 = compile_one("store", "storeName");
var m4 = merge_rules(m3,merge_rules(m1, m2));
var out = m4.exec(input);
alert(JSON.stringify(out));
I have borrowed earlier answer and made improvements so as to solve both your examples and this should be generic. Though if you plan to run this sequencially with 2 sets of inputs, then the behavior will be as I have outlined in my comments to your original question.
var apiObj = {
items: [{
name: "Hammer",
skus: [{
num: "12345qwert"
}]
}, {
name: "Bike",
skus: [{
num: "asdfghhj"
}, {
num: "zxcvbn"
}]
}, {
name: "Fork",
skus: [{
num: "0987dfgh"
}]
}]
};
var myObj = { //Previously has values
storeName: "",
items: [{
uniqueName: ""
}],
outputModel: {
items: [{
name: "Hammer"
}]
}
};
/** Also works with this **
var myPath = "outputModel.items[].uniqueName";
var apiPath = "items[].name";
*/
var myPath = "outputModel.items[].sku";
var apiPath = "items[].skus[].num";
function make_accessor(program) {
return function (obj, callback) {
(function do_segment(obj, segments) {
var start = segments.shift() // Get first segment
var pieces = start.match(/(\w+)(\[\])?/); // Get name and [] pieces
var property = pieces[1];
var isArray = pieces[2]; // [] on end
obj = obj[property]; // drill down
if (!segments.length) { // last segment; callback
if (isArray) {
return obj.forEach(callback);
} else {
return callback(obj);
}
} else { // more segments; recurse
if (isArray) { // array--loop over elts
obj.forEach(function (elt) {
do_segment(elt, segments.slice());
});
} else {
do_segment(obj, segments.slice()); // scalar--continue
}
}
})(obj, program.split('.'));
};
}
function make_inserter(program) {
return function (obj, value) {
(function do_segment(obj, segments) {
var start = segments.shift() // Get first segment
var pieces = start.match(/(\w+)(\[\])?/); // Get name and [] pieces
var property = pieces[1];
var isArray = pieces[2]; // [] on end
if (segments.length) { // more segments
if (!obj[property]) {
obj[property] = isArray ? [] : {};
}
do_segment(obj[property], segments.slice());
} else { // last segment
if (Array.isArray(obj)) {
var addedInFor = false;
for (var i = 0; i < obj.length; i++) {
if (!(property in obj[i])) {
obj[i][property] = value;
addedInFor = true;
break;
}
}
if (!addedInFor) {
var entry = {};
entry[property] = value;
obj.push(entry);
}
} else obj[property] = value;
}
})(obj, program.split('.'));
};
}
access = make_accessor(apiPath);
insert = make_inserter(myPath);
access(apiObj, function (val) {
insert(myObj, val);
});
console.log(myObj);
(old solution: https://jsfiddle.net/d7by0ywy/):
Here is my new generalized solution when you know the two objects to process in advance (called inp and out here). If you don't know them in advance you can use the trick in the old solution to assign the objects on both sides of = to inp and out (https://jsfiddle.net/uxdney3L/3/).
Restrictions: There has to be the same amount of arrays on both sides and an array has to contain objects. Othewise it would be ambiguous, you would have to come up with a better grammar to express rules (or why don't you have functions instead of rules?) if you want it to be more sophisticated.
Example of ambiguity: out.items[].sku=inp[].skus[].num Do you assign an array of the values of num to sku or do you assign an array of objects with the num property?
Data:
rules = [
'out.items[].name=inp[].name',
'out.items[].sku[].num=inp[].skus[].num'
];
inp = [{
'name': 'Hammer',
'skus':[{'num':'12345qwert','test':'ignore'}]
},{
'name': 'Bike',
'skus':[{'num':'asdfghhj'},{'num':'zxcvbn'}]
},{
'name': 'Fork',
'skus':[{'num':'0987dfgh'}]
}];
Program:
function process() {
if (typeof out == 'undefined') {
out = {};
}
var j, r;
for (j = 0; j < rules.length; j++) {
r = rules[j].split('=');
if (r.length != 2) {
console.log('invalid rule: symbol "=" is expected exactly once');
} else if (r[0].substr(0, 3) != 'out' || r[1].substr(0, 3) != 'inp') {
console.log('invalid rule: expected "inp...=out..."');
} else {
processRule(r[0].substr(3).split('[]'), r[1].substr(3).split('[]'), 0, inp, out);
}
}
}
function processRule(l, r, n, i, o) { // left, right, index, in, out
var t = r[n].split('.');
for (var j = 0; j < t.length; j++) {
if (t[j] != '') {
i = i[t[j]];
}
}
t = l[n].split('.');
if (n < l.length - 1) {
for (j = 0; j < t.length - 1; j++) {
if (t[j] != '') {
if (typeof o[t[j]] == 'undefined') {
o[t[j]] = {};
}
o = o[t[j]];
}
}
if (typeof o[t[j]] == 'undefined') {
o[t[j]] = [];
}
o = o[t[j]];
for (j = 0; j < i.length; j++) {
if (typeof o[j] == 'undefined') {
o[j] = {};
}
processRule(l, r, n + 1, i[j], o[j]);
}
} else {
for (j = 0; j < t.length - 1; j++) {
if (t[j] != '') {
if (typeof o[t[j]] == 'undefined') {
o[t[j]] = {};
}
o = o[t[j]];
}
}
o[t[j]] = i;
}
}
process();
console.log(out);
Well, an interesting problem. Programmatically constructing nested objects from a property accessor string (or the reverse) isn't much of a problem, even doing so with multiple descriptors in parallel. Where it does get complicated are arrays, which require iteration; and that isn't as funny any more when it gets to different levels on setter and getter sides and multiple descriptor strings in parallel.
So first we need to distinguish the array levels of each accessor description in the script, and parse the text:
function parse(script) {
return script.split(/\s*[;\r\n]+\s*/g).map(function(line) {
var assignment = line.split(/\s*=\s*/);
return assignment.length == 2 ? assignment : null; // console.warn ???
}).filter(Boolean).map(function(as) {
as = as.map(function(accessor) {
var parts = accessor.split("[]").map(function(part) {
return part.split(".");
});
for (var i=1; i<parts.length; i++) {
// assert(parts[i][0] == "")
var prev = parts[i-1][parts[i-1].length-1];
parts[i][0] = prev.replace(/s$/, ""); // singular :-)
}
return parts;
});
if (as[0].length == 1 && as[1].length > 1) // getter contains array but setter does not
as[0].unshift(["output"]); // implicitly return array (but better throw an error)
return {setter:as[0], getter:as[1]};
});
}
With that, the textual input can be made into a usable data structure, and now looks like this:
[{"setter":[["outputModel","items"],["item","name"]],
"getter":[["items"],["item","name"]]},
{"setter":[["outputModel","items"],["item","sku"]],
"getter":[["items"],["item","skus"],["sku","num"]]}]
The getters already transform nicely into nested loops like
for (item of items)
for (sku of item.skus)
… sku.num …;
and that's exactly where we are going to. Each of those rules is relatively easy to process, copying properties on objects and iterating array for array, but here comes our most crucial issue: We have multiple rules. The basic solution when we deal with iterating multiple arrays is to create their cartesian product and this is indeed what we will need. However, we want to restrict this a lot - instead of creating every combination of all names and all nums in the input, we want to group them by the item that they come from.
To do so, we'll build some kind of prefix tree for our output structure that'll contain generators of objects, each of those recursivley being a tree for the respective output substructure again.
function multiGroupBy(arr, by) {
return arr.reduce(function(res, x) {
var p = by(x);
(res[p] || (res[p] = [])).push(x);
return res;
}, {});
}
function group(rules) {
var paths = multiGroupBy(rules, function(rule) {
return rule.setter[0].slice(1).join(".");
});
var res = [];
for (var path in paths) {
var pathrules = paths[path],
array = [];
for (var i=0; i<pathrules.length; i++) {
var rule = pathrules[i];
var comb = 1 + rule.getter.length - rule.setter.length;
if (rule.setter.length > 1) // its an array
array.push({
generator: rule.getter.slice(0, comb),
next: {
setter: rule.setter.slice(1),
getter: rule.getter.slice(comb)
}
})
else if (rule.getter.length == 1 && i==0)
res.push({
set: rule.setter[0],
get: rule.getter[0]
});
else
console.error("invalid:", rule);
}
if (array.length)
res.push({
set: pathrules[0].setter[0],
cross: product(array)
});
}
return res;
}
function product(pathsetters) {
var groups = multiGroupBy(pathsetters, function(pathsetter) {
return pathsetter.generator[0].slice(1).join(".");
});
var res = [];
for (var genstart in groups) {
var creators = groups[genstart],
nexts = [],
nests = [];
for (var i=0; i<creators.length; i++) {
if (creators[i].generator.length == 1)
nexts.push(creators[i].next);
else
nests.push({path:creators[i].path, generator: creators[i].generator.slice(1), next:creators[i].next});
}
res.push({
get: creators[0].generator[0],
cross: group(nexts).concat(product(nests))
});
}
return res;
}
Now, our ruleset group(parse(script)) looks like this:
[{
"set": ["outputModel","items"],
"cross": [{
"get": ["items"],
"cross": [{
"set": ["item","name"],
"get": ["item","name"]
}, {
"get": ["item","skus"],
"cross": [{
"set": ["item","sku"],
"get": ["sku","num"]
}]
}]
}]
}]
and that is a structure we can actually work with, as it now clearly conveys the intention on how to match together all those nested arrays and the objects within them.
Let's dynamically interpret this, building an output for a given input:
function transform(structure, input, output) {
for (var i=0; i<structure.length; i++) {
output = assign(output, structure[i].set.slice(1), getValue(structure[i], input));
}
return output;
}
function retrieve(val, props) {
return props.reduce(function(o, p) { return o[p]; }, val);
}
function assign(obj, props, val) {
if (!obj)
if (!props.length) return val;
else obj = {};
for (var j=0, o=obj; j<props.length-1 && o!=null && o[props[j]]; o=o[props[j++]]);
obj[props[j]] = props.slice(j+1).reduceRight(function(val, p) {
var o = {};
o[p] = val;
return o;
}, val);
return obj;
}
function getValue(descriptor, input) {
if (descriptor.get) // && !cross
return retrieve(input, descriptor.get.slice(1));
var arr = [];
descriptor.cross.reduce(function horror(next, d) {
if (descriptor.set)
return function (inp, cb) {
next(inp, function(res){
cb(assign(res, d.set.slice(1), getValue(d, inp)));
});
};
else // its a crosser
return function(inp, cb) {
var g = retrieve(inp, d.get.slice(1)),
e = d.cross.reduce(horror, next)
for (var i=0; i<g.length; i++)
e(g[i], cb);
};
}, function innermost(inp, cb) {
cb(); // start to create an item
})(input, function(res) {
arr.push(res); // store the item
});
return arr;
}
And this does indeed work with
var result = transform(group(parse(script)), items); // your expected result
But we can do better, and much more performant:
function compile(structure) {
function make(descriptor) {
if (descriptor.get)
return {inputName: descriptor.get[0], output: descriptor.get.join(".") };
var outputName = descriptor.set[descriptor.set.length-1];
var loops = descriptor.cross.reduce(function horror(next, descriptor) {
if (descriptor.set)
return function(it, cb) {
return next(it, function(res){
res.push(descriptor)
return cb(res);
});
};
else // its a crosser
return function(it, cb) {
var arrName = descriptor.get[descriptor.get.length-1],
itName = String.fromCharCode(it);
var inner = descriptor.cross.reduce(horror, next)(it+1, cb);
return {
inputName: descriptor.get[0],
statement: (descriptor.get.length>1 ? "var "+arrName+" = "+descriptor.get.join(".")+";\n" : "")+
"for (var "+itName+" = 0; "+itName+" < "+arrName+".length; "+itName+"++) {\n"+
"var "+inner.inputName+" = "+arrName+"["+itName+"];\n"+
inner.statement+
"}\n"
};
};
}, function(_, cb) {
return cb([]);
})(105, function(res) {
var item = joinSetters(res);
return {
inputName: item.inputName,
statement: (item.statement||"")+outputName+".push("+item.output+");\n"
};
});
return {
statement: "var "+outputName+" = [];\n"+loops.statement,
output: outputName,
inputName: loops.inputName
};
}
function joinSetters(descriptors) {
if (descriptors.length == 1 && descriptors[0].set.length == 1)
return make(descriptors[0]);
var paths = multiGroupBy(descriptors, function(d){ return d.set[1] || console.error("multiple assignments on "+d.set[0], d); });
var statements = [],
inputName;
var props = Object.keys(paths).map(function(p) {
var d = joinSetters(paths[p].map(function(d) {
var names = d.set.slice(1);
names[0] = d.set[0]+"_"+names[0];
return {set:names, get:d.get, cross:d.cross};
}));
inputName = d.inputName;
if (d.statement)
statements.push(d.statement)
return JSON.stringify(p) + ": " + d.output;
});
return {
inputName: inputName,
statement: statements.join(""),
output: "{"+props.join(",")+"}"
};
}
var code = joinSetters(structure);
return new Function(code.inputName, code.statement+"return "+code.output+";");
}
So here is what you will get in the end:
> var example = compile(group(parse("outputModel.items[].name = items[].name;outputModel.items[].sku = items[].skus[].num;")))
function(items) {
var outputModel_items = [];
for (var i = 0; i < items.length; i++) {
var item = items[i];
var skus = item.skus;
for (var j = 0; j < skus.length; j++) {
var sku = skus[j];
outputModel_items.push({"name": item.name,"sku": sku.num});
}
}
return {"items": outputModel_items};
}
> var flatten = compile(group(parse("as[]=bss[][]")))
function(bss) {
var as = [];
for (var i = 0; i < bss.length; i++) {
var bs = bss[i];
for (var j = 0; j < bs.length; j++) {
var b = bs[j];
as.push(b);
}
}
return as;
}
> var parallelRecords = compile(group(parse("x.as[]=y[].a; x.bs[]=y[].b")))
function(y) {
var x_as = [];
for (var i = 0; i < y.length; i++) {
var y = y[i];
x_as.push(y.a);
}
var x_bs = [];
for (var i = 0; i < y.length; i++) {
var y = y[i];
x_bs.push(y.b);
}
return {"as": x_as,"bs": x_bs};
}
And now you can easily pass your input data to that dynamically created function and it will be transformed quite fast :-)

Readability of Javascript Array

I have a JSON OBJECT similar to following
{
"kay1":"value1",
"key2":"value2",
"key3":{
"key31":"value31",
"key32":"value32",
"key33":"value33"
}
}
I want to replace that with JSON ARRAY as follows
[
"value1",
"value2",
[
"value31",
"value32",
"value33"
]
]
My motivation to change the the JSON OBJECT to JSON ARRAY is it takes less amount of network traffic, getting the value from ARRAY is efficient than OBJECT, etc.
One problem I face is the readability of the ARRAY is very less than OBJECT.
Is there any way to improve the readability?
Here you go, I have written a function which will convert all instances of object to array and give you the result you are expecting.
var objActual = {
"key1":"value1",
"key2":"value2",
"key3":{
"key31":"value31",
"key32":"value32",
"key33": {
"key331" : "value331",
"key332" : "value332"
}
}
};
ObjectUtil = {
isObject : function(variable) {
if(Object.prototype.toString.call(variable) === '[object Object]') {
return true;
}
return false;
},
convertToArray : function(obj) {
var objkeys = Object.keys(obj);
var arr = [];
objkeys.forEach(function(key) {
var objectToPush;
if(ObjectUtil.isObject(obj[key])) {
objectToPush = ObjectUtil.convertToArray(obj[key]);
} else {
objectToPush = obj[key];
}
arr.push(objectToPush);
});
return arr;
}
};
var result = ObjectUtil.convertToArray(objActual);
console.log(result);
In my opinion, it should be:
{
"kay1":"value1",
"key2":"value2",
"key3":["value31", "value32", "value33"]
}
Using the init method is time critical. So use a scheme or assign static JSON to Storage.keys and assign your bulk data array to store.data. You can use store.get("key3.key31") after that. http://jsfiddle.net/2o411k00/
if (!Array.prototype.map)
{
Array.prototype.map = function(fun /*, thisp*/)
{
var len = this.length;
if (typeof fun != "function")
throw new TypeError();
var res = new Array(len);
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in this)
res[i] = fun.call(thisp, this[i], i, this);
}
return res;
};
}
var data = {
"kay1":"value1",
"key2":"value2",
"key3":{
"key31":"value31",
"key32":"value32",
"key33":"value33"
}
}
var Storage = function(data){
this.rawData = data;
return this;
}
Storage.prototype.init = function(){
var self = this;
var index = 0;
var mp = function(dat, rootKey){
var res = Object.keys(dat).map(function(key, i) {
var v = dat[key];
if (typeof(v) === 'object'){
mp(v, key);
} else {
self.data.push(v);
var nspace = rootKey.split(".").concat([key]).join(".");
self.keys[nspace] = index++;
}
});
}
mp(this.rawData, "");
}
Storage.prototype.get = function(key){
return this.data[this.keys[key]];
};
Storage.prototype.data = [];
Storage.prototype.keys = {};
var store = new Storage(data);
console.log(data);
store.init();
console.log("keys", store.keys);
console.log("data", store.data);
console.log("kay1=", store.get(".kay1"));
console.log("key2=", store.get(".key2"));
console.log("key3.key31=", store.get("key3.key31"));
console.log("key3.key32=",store.get("key3.key32"));
console.log("key3.key33=", store.get("key3.key33"));

How to filter elements in array in JavaScript object

If I have a JavaScript object like this:
{"products":
[
{
"id":"6066157707315577",
"reference_prefix":"BB",
"name":"BeanieBaby",
"product_line":false,
"has_ideas":true
},
{
"id":"6066197229601550",
"reference_prefix":"BBAGS",
"name":"BlackBags",
"product_line":false,
"has_ideas":false
}
],
"pagination": {
"total_records":4,
"total_pages":1,
"current_page":1
}
}
How do I write a function in js to loop over each pair and only return the elements of the array where has_ideas === true?
I have started with this but I'm stuck. Clearly I am new to this. Any help appreciated.
product: function(mybundle) {
var json = JSON.parse(mybundle.response.content);
for(var i = 0; i < json.length; i++) {
var obj = json[i];
if (json[i].id === "has_ideas" && json[i].value === true) {
return json;
}
return [];
}
}
You can filter out each pair by simply checking that property:
var json = {"products":[{"id":"6066157707315577","reference_prefix":"BB","name":"BeanieBaby","product_line":false,"has_ideas":true},{"id":"6066197229601550","reference_prefix":"BBAGS","name":"BlackBags","product_line":false,"has_ideas":false}],"pagination":{"total_records":4,"total_pages":1,"current_page":1}}
var stuff = json.products.filter(function(obj) {
return obj.has_ideas === true
});
console.log(stuff);
Demo:http://jsfiddle.net/bsyk18cb/
try this
product: function(mybundle) {
var json = JSON.parse(mybundle.response.content);
for(var i = 0; i < json.length; i++) {
if(json[i].has_ideas === true){
return json;
}
return [];
}
}
You want to check the "has_ideas" attribute and if true, return the id.
product: function(mybundle) {
var json = JSON.parse(mybundle.response.content);
for(var i = 0; i < json.length; i++) {
if (json[i].has_ideas === true) {
return json[i].id;
}
return [];
}
}
Use code below.
this will return array of elements having has_ideas=true
var json = "{'products':"+
"["+
"{"+
"'id':'6066157707315577',"+
"'reference_prefix':'BB',"+
"'name':'BeanieBaby',"+
"'product_line':false,"+
"'has_ideas':true"+
"},"+
"{"+
"'id':'6066197229601550',"+
"'reference_prefix':'BBAGS',"+
"'name':'BlackBags',"+
"'product_line':false,"+
"'has_ideas':false"+
"}"+
"],"+
"'pagination': {"+
"'total_records':4,"+
"'total_pages':1,"+
"'current_page':1"+
"}"+
"}";
function filter(){
var jsonArr = [];
var gList = eval( "(" + json + ")");
alert(gList.products.length);
for(var i=0;i<gList.products.length;i++){
if(gList.products[i].has_ideas){
jsonArr.push(gList.products[i]);
}
}
return jsonArr;
}
Demo

SOAP response (XML) to JSON

I need to consume a SOAP web service which, naturally, sends its response in XML, since I'm developing a Appcelerator Titanium mobile app I would prefer the response in JSON. After looking online I converted the response using this Javascript code, it mostly worked but returned results such as the following:
{
"SOAP-ENV:Body" : {
"ns1:linkAppResponse" : {
"ns1:result" : {
#text : true;
};
"ns1:uuid" : {
#text : "a3dd915e-b4e4-43e0-a0e7-3c270e5e7aae";
};
};
};
}
Of course the colons and hashes in the caused problems so I adjusted the code to do a substring on the name and drop off anything before the ':', then a stringified the resulting JSON, removed all the hashes and parsed the JSON again. This is a bit messy for my liking but I end up with something usable.
Here is the xmlToJson code I'm using:
// Changes XML to JSON
function xmlToJson(xml) {
// Create the return object
var obj = {};
if (xml.nodeType == 1) {// element
// do attributes
if (xml.attributes.length > 0) {
obj["#attributes"] = {};
for (var j = 0; j < xml.attributes.length; j++) {
var attribute = xml.attributes.item(j);
obj["#attributes"][attribute.nodeName] = attribute.nodeValue;
}
}
} else if (xml.nodeType == 3) {// text
obj = xml.nodeValue;
}
// do children
if (xml.hasChildNodes()) {
for (var i = 0; i < xml.childNodes.length; i++) {
var item = xml.childNodes.item(i);
var nodeName = item.nodeName.substring(item.nodeName.indexOf(":") + 1);
if ( typeof (obj[nodeName]) == "undefined") {
obj[nodeName] = xmlToJson(item);
} else {
if ( typeof (obj[nodeName].push) == "undefined") {
var old = obj[nodeName];
obj[nodeName] = [];
obj[nodeName].push(old);
}
obj[nodeName].push(xmlToJson(item));
}
}
}
return obj;
};
module.exports = xmlToJson;
Which results in the following JSON:
{
Body : {
linkAppResponse : {
result : {
text : true;
};
uuid : {
text : "9022d249-ea8a-47a3-883c-0f4cfc9d6494";
};
};
};
}
While this returns a JSON object I can use, I would prefer to have the resulting JSON in the following form:
{
result : true;
uuid : "9022d249-ea8a-47a3-883c-0f4cfc9d6494";
};
Mostly so it's less verbose and I can simply call json.result in order check if the query was successful instead of json.Body.linkAppResponse.result.text
Any help is greatly appreciated.
Came up with a working solution, not any less dirty but it works and returns data in the format I want.
function soapResponseToJson(xml) {
var json = xmlToJson(xml).Body;
console.debug(json);
var response = {};
for (var outterKey in json) {
if (json.hasOwnProperty(outterKey)) {
temp = json[outterKey];
for (var innerKey in temp) {
if (temp.hasOwnProperty(innerKey)) {
response[innerKey] = temp[innerKey].text;
}
}
}
}
console.debug(response);
return response;
}
// Changes XML to JSON
function xmlToJson(xml) {
// Create the return object
var obj = {};
if (xml.nodeType == 1) {// element
// do attributes
if (xml.attributes.length > 0) {
obj["#attributes"] = {};
for (var j = 0; j < xml.attributes.length; j++) {
var attribute = xml.attributes.item(j);
obj["#attributes"][attribute.nodeName] = attribute.nodeValue;
}
}
} else if (xml.nodeType == 3) {// text
obj = xml.nodeValue;
}
// do children
if (xml.hasChildNodes()) {
for (var i = 0; i < xml.childNodes.length; i++) {
var item = xml.childNodes.item(i);
var nodeName = item.nodeName.substring(item.nodeName.indexOf(":") + 1).replace('#', '');
if ( typeof (obj[nodeName]) == "undefined") {
obj[nodeName] = xmlToJson(item);
} else {
if ( typeof (obj[nodeName].push) == "undefined") {
var old = obj[nodeName];
obj[nodeName] = [];
obj[nodeName].push(old);
}
obj[nodeName].push(xmlToJson(item));
}
}
}
return obj;
};
module.exports = soapResponseToJson;
console.debug(json):
{
linkAppResponse : {
result : {
text : true;
};
uuid : {
text : "e4f78c5f-1bc2-4b50-a749-19d733b9be3f";
};
};
}
console.debug(response):
{
result : true;
uuid : "e4f78c5f-1bc2-4b50-a749-19d733b9be3f";
}
I'm going to leave this question open for a while in case someone comes up with a better solution.
I feel like this is a fairly ugly solution (hope it doesn't offend you :) ).
Why don't you marshal the xml to an object and then use gson or jackson to map to json.
I don't know what framework you use, in spring for example, you can use jaxb2 to marshal and jackson or gson to transform your object to json.

Categories

Resources