Javascript: Parse array like search querystring - javascript

This is a string i have in my javascript
var searchString = City=20&Region=67&&Interests[8]=8&Interests[13]=13&Interests[4]=4&Duration[1]=Full+Day+Tour&Duration[3]=Evening+Tour&Duration[5]=2+Day+Short+Break&Departs[Fri]=Fri&Departs[Sat]=Sat&Departs[Sun]=Sun&TourLanguages=1&action_doSearch=Update
and i have a function
function loadDataFrom(request){
//if request if empty then return
if(request == "")
return;
var request = decodeURIComponent(request);
//if we get there then its likely we have a search query to process
var searchCriteria = request.split('&');
var hash = {};
for(var i = 0; i < searchCriteria.length; i++) {
var val = searchCriteria[i].split('=');
//we can safely ignore the "view" and 'action_doSearch' becuase they are not searched on
if(unescape(val[0]) === 'view' || unescape(val[0]) === 'action_doSearch')
continue;
//filter objects without any values
if(val[1] != '')
//add the names and values to our object hash
hash[unescape(val[0])] = unescape(val[1]);
}
//iterate over the hash objects and apply the current selection
$.each(hash, function(index, value) {
switch (index) {
case 'City':
case 'Region':
case 'TourLanguages':
//do stuff;
break;
case 'Duration[]':
case 'Departs[]':
//do something esle
default:
break;
}
});
};
that parses the URL parameters into an objecct hash with the following values.
City: "20"
Region: "67"
Departs[Fri]: "Fri"
Departs[Sat]: "Sat"
Departs[Sun]: "Sun"
Duration[1]: "Full+Day+Tour"
Duration[3]: "Evening+Tour"
Duration[5]: "2+Day+Short+Break"
Interests[4]: "4"
Interests[8]: "8"
Interests[13]: "13"
TourLanguages: "1"
but what i'd really like to do is to seperate the url into array like values like so
City: "20"
Region: "67"
Departs: ["Fri","Sat","Sun"]
Duration: ["Full+Day+Tour", "Evening+Tour", "2+Day+Short+Break"]
Interests: ["4", "8", "13"]
TourLanguages: "1"
Any help/pointers on this problem is greatly appreciated. Thanks in Advance

For something like this, to make it easier on myself I would write a RegExp to get the parts, then do some if logic to decide how to construct the Object.
var searchString = "City=20&Region=67&&Interests[8]=8&Interests[13]=13&Interests[4]=4&Duration[1]=Full+Day+Tour&Duration[3]=Evening+Tour&Duration[5]=2+Day+Short+Break&Departs[Fri]=Fri&Departs[Sat]=Sat&Departs[Sun]=Sun&TourLanguages=1&action_doSearch=Update",
o = {};
('&' + searchString)
.replace(
/&([^\[=&]+)(\[[^\]]*\])?(?:=([^&]*))?/g,
function (m, $1, $2, $3) {
if ($2) {
if (!o[$1]) o[$1] = [];
o[$1].push($3);
} else o[$1] = $3;
}
);
o; /*
{
"City": "20",
"Region": "67",
"Interests": ["8", "13", "4"],
"Duration": ["Full+Day+Tour", "Evening+Tour", "2+Day+Short+Break"],
"Departs": ["Fri", "Sat", "Sun"],
"TourLanguages": "1",
"action_doSearch": "Update"
} */

I would do it this way:
var str = 'City=20&Region=67&&Interests[8]=8&Interests[13]=13&Interests[4]=4&Duration[1]=Full+Day+Tour&Duration[3]=Evening+Tour&Duration[5]=2+Day+Short+Break&Departs[Fri]=Fri&Departs[Sat]=Sat&Departs[Sun]=Sun&TourLanguages=1&action_doSearch=Update',
strsplit = str.split(/&+/),
o = {};
for (var i = 0, l = strsplit.length; i < l; i++) {
var r = strsplit[i].match(/^([^=\[\]]+)(?:\[[^\]]+\])?=(.*)$/);
if (o[r[1]] === undefined) {
o[r[1]] = r[2];
} else if (o[r[1]].push) {
o[r[1]].push(r[2]);
} else {
o[r[1]] = [o[r[1]], r[2]];
}
}

This is a perfect scenario to use Map-Reduce and I recommend you to use underscore.js to implement something simple, elegant and more readable solution.
var m = _.map(searchString.split('&'), function (item) {
var parts = item.split('='), names = parts[0].split('[');
return [names[0], parts[1]];
});
var result = _.reduce(m, function(memo, item){
var key = item[0], value = item[1];
if(memo[key] === undefined) memo[key] = [value]
else memo[key].push(value)
return memo;
}, {});
console.log(result);

Related

Javascript Parsing Key Value String to JSON

I'm currently working on trying to create a UDF to split a key value pair string based on web traffic into JSON.
I've managed to get as far as outputting a JSON object but I'd like to be able to dynamically add nested items based on the number of products purchased or viewed based on the index number of the key.
When a product is only viewed, there is always only one product in the string. Only when its a transaction is it more than one but I think it would be good to conform the structure of the json and then identify a purchase or view based on the presence of a transactionid. For example:
Item Purchased:
sessionid=12345&transactionid=555555&product1=apples&productprice1=12&product1qty=1&product2=pears&productprice2=23&product2qty=3&transactionamount=58
The output should look something like this:
[
{
"sessionid":12345,
"transactionid":555555,
"transactionamount":58
},
[
{
"productline":1,
"product":"apples",
"productprice":12,
"productqty":1
},
{
"productline":2,
"product":"pears",
"productprice":23,
"productqty":2
}
]
]
Item Viewed:
sessionid=12345&product1=apples&productprice1=12&product1qty=1&product2=pears&productprice2=23&product2qty=3
[
{
"sessionid":12345,
"transactionid":0,
"transactionamount":0
},
[
{
"productline":1,
"product":"apples",
"productprice":12,
"productqty":1
}
]
]
The result I'll be able to parse from JSON into a conformed table in a SQL table.
What I've tried so far is only parsing the string, but its not ideal to create a table in SQL because the number of purchases can vary:
var string = "sessionid=12345&transactionid=555555&product1=apples&productprice1=12&product1qty=1&product2=pears&productprice2=23&product2qty=3&transactionamount=58";
function splitstring(queryString) {
var dictionary = {};
if (queryString.indexOf('?') === 0) {
queryString = queryString.substr(1);
}
var parts = queryString.split('&');
for (var i = 0; i < parts.length; i++) {
var p = parts[i];
// Step 2: Split Key/Value pair
var keyValuePair = p.split('=');
var key = keyValuePair[0];
var value = keyValuePair[1];
dec_val = decodeURIComponent(value);
final_value = dec_val.replace(/\+/g, ' ');
dictionary[key] = final_value;
}
return (dictionary);
}
console.log(splitstring(string));
Thanks in advance!!!
Feel like this would be less clunky with better param naming conventions, but here's my take...
function parseString(string) {
var string = string || '',
params, param, output, i, l, n, v, k, pk;
params = string.split('&');
output = [{},
[]
];
for (i = 0, l = params.length; i < l; i++) {
param = params[i].split('=');
n = param[0].match(/^product.*?([0-9]+).*/);
v = decodeURIComponent(param[1] || '');
if (n && n[1]) {
k = n[1];
output[1][k] = output[1][k] || {};
output[1][k]['productline'] = k;
pk = n[0].replace(/[0-9]+/, '');
output[1][k][pk] = v;
} else {
output[0][param[0]] = v;
}
}
output[1] = output[1].filter(Boolean);
return output;
}
var string = "sessionid=12345&transactionid=555555&product1=apples&productprice1=12&product1qty=1&product2=pears&productprice2=23&product2qty=3&transactionamount=58";
console.log(parseString(string));
output:
[
{
"sessionid": "12345",
"transactionid": "555555",
"transactionamount": "58"
},
[{
"productline": "1",
"product": "1",
"productprice": "12"
}, {
"productline": "2",
"product": "3",
"productprice": "23"
}]
]
There's probably a far nicer way to do this, but I just wrote code as I thought about it
var string = "sessionid=12345&transactionid=555555&product1=apples&productprice1=12&product1qty=1&product2=pears&productprice2=23&product2qty=3&transactionamount=58";
function splitstring(queryString) {
var dictionary = {};
if (queryString.indexOf('?') === 0) {
queryString = queryString.substr(1);
}
var parts = queryString.split('&');
for (var i = 0; i < parts.length; i++) {
var p = parts[i];
// Step 2: Split Key/Value pair
var keyValuePair = p.split('=');
var key = keyValuePair[0];
var value = keyValuePair[1];
dec_val = decodeURIComponent(value);
final_value = dec_val.replace(/\+/g, ' ');
dictionary[key] = final_value;
}
return (dictionary);
}
function process(obj) {
let i = 1;
const products = [];
while(obj.hasOwnProperty(`product${i}`)) {
products.push({
[`product`]: obj[`product${i}`],
[`productprice`]: obj[`productprice${i}`],
[`productqty`]: obj[`product${i}qty`]
});
delete obj[`product${i}`];
delete obj[`productprice${i}`];
delete obj[`product${i}qty`];
++i;
}
return [obj, products];
}
console.log(process(splitstring(string)));
By the way, if this is in the browser, then splitstring can be "replaced" by
const splitstring = string => Object.fromEntries(new URLSearchParams(string).entries());
var string = "sessionid=12345&transactionid=555555&product1=apples&productprice1=12&product1qty=1&product2=pears&productprice2=23&product2qty=3&transactionamount=58";
function process(string) {
const splitstring = queryString => {
var dictionary = {};
if (queryString.indexOf('?') === 0) {
queryString = queryString.substr(1);
}
var parts = queryString.split('&');
for (var i = 0; i < parts.length; i++) {
var p = parts[i];
// Step 2: Split Key/Value pair
var keyValuePair = p.split('=');
var key = keyValuePair[0];
var value = keyValuePair[1];
dec_val = decodeURIComponent(value);
final_value = dec_val.replace(/\+/g, ' ');
dictionary[key] = final_value;
}
return (dictionary);
};
let i = 1;
const obj = splitstring(string);
const products = [];
while (obj.hasOwnProperty(`product${i}`)) {
products.push({
[`product`]: obj[`product${i}`],
[`productprice`]: obj[`productprice${i}`],
[`productqty`]: obj[`product${i}qty`]
});
delete obj[`product${i}`];
delete obj[`productprice${i}`];
delete obj[`product${i}qty`];
++i;
}
return [obj, products];
}
console.log(process(string));

Looping dynamic keys in an object cant seem to get arrays within to output

I'm currently trying to loop through an object, in that object I have a mixture of objects and arrays.
The Object
var templateObject = {
"addressbook": {
"streetaddress": ["streetaddress1", "1"],
"country": ["country", "2"]
},
"companyname": ["thecompanyname", "1"],
"email": ["theemail", "1"]
};
Looping the object, we won't always know the names of the keys that are in the object, so I'm trying to loop them
for(var prop in templateObject)
{
document.write(prop);
if(templateObject.hasOwnProperty(prop))
{
for(var subItem in templateObject[prop])
{
var currentItem = templateObject[prop][subItem];
document.write('<b>' + currentItem[0] + '</b><br/>');
document.write(currentItem[1]);
document.write('<hr/>');
}
}
}
it's currently returning:-
addressbook streetaddress1 1 country 2 company name th 1 undefined
email t h 1 undefined
I seem to be able to get the addressbook object without any issues, but I can't seem to get companyname or email out and print it to the screen.
I'm hoping someone can help me with this as I've been struggling with this one for a while
I have also prepared a fiddle here: https://jsfiddle.net/dimmers/97mqke0f/
thanks in advance
Try Object.keys function on your object, it is very nice.
Doing Object.keys(templateObject) should give you addressbook, companyname, email
Also var in is only for objects. It won't work on companyname or email as they are arrays.
This works for me:
var templateObject = {
"addressbook": {
"streetaddress": ["streetaddress1", "1"],
"country": ["country", "2"]
},
"companyname": ["thecompanyname", "1"],
"email": ["theemail", "1"]
};
function writeIt(currentItem) {
// currentItem should be array
document.write('<b>' + currentItem[0] + '</b><br/>');
document.write(currentItem[1]);
document.write('<hr/>');
}
for (var prop in templateObject) {
document.write(prop);
if (Object.prototype.toString.call(templateObject[prop]) == '[object Object]') {
for (var subItem in templateObject[prop]) {
var currentItem = templateObject[prop][subItem];
writeIt(currentItem);
}
} else if (Object.prototype.toString.call(templateObject[prop]) == '[object Array]') {
writeIt(templateObject[prop]);
}
}
Updated fiddle:
https://jsfiddle.net/97mqke0f/1/
You can create a function to get the keys, then call it recursively for properties that are objects (i.e. typeof value == 'object'). But you might want to not iterate over values that are Arrays, so check using Array.isArray first.
e.g.
function showKeys(obj) {
// Get the keys of object and show them
var keys = Object.keys(obj);
document.write('<br>' + keys.join(', '));
// Check for values that are Objects, ignore Arrays, and call recursively
keys.forEach(function(key) {
var value = obj[key];
if (typeof value == 'object' && !Array.isArray(value)) {
showKeys(value);
}
});
}
var templateObject = {
"addressbook": {
"streetaddress": ["streetaddress1", "1"],
"country": ["country", "2"]
},
"companyname": ["thecompanyname", "1"],
"email": ["theemail", "1"]
};
showKeys(templateObject);
This will cope with any depth of nesting, but won't deal with Objects inside Arrays, but it's trivial to get those too (loop over Arrays as well but don't print their keys).
I encountered similar problem before, and I wrote a function to solve it. Here is my code, it will loop through a object and output all the enumerable properties.
function joOutput(o, decodeUrl) {
var txt, depth, sp, sub, isEmpty;
if (typeof o !== "object")
return "[Not an object]";
isEmpty = function(e) {
var i;
for (i in e)
return false;
return true;
};
if (isEmpty(o))
return "[Empty Object]";
txt = "<b>NOTE:</b>n for attribute name, d for depth, v for value.<br>";
txt += "-----------------------------------<br>";
depth = 0;
sp = function(n) {
var s = "";
for (var i = 0; i < n; i++) {
s += "&nbsp&nbsp&nbsp&nbsp&nbsp.";
}
return s;
};
sub = function(obj) {
var attr;
for (attr in obj) {
if ((typeof obj[attr]) !== "object") {
if (decodeUrl)
obj[attr] = decodeURIComponent(obj[attr]);
txt += sp(depth) + "[n: " + attr + " - d: " + depth + " - v: <b>" + obj[attr] + "</b>]<br>";
} else {
txt += sp(depth) + "[n:" + attr + " - d:" + depth + "]...<br>";
depth++;
arguments.callee(obj[attr]);
}
}
depth--;
return txt;
};
return sub(o);
}
// Test:
var templateObject = {
"addressbook": {
"streetaddress": ["streetaddress1", "1"],
"country": ["country", "2"]
},
"companyname": ["thecompanyname", "1"],
"email": ["theemail", "1"]
};
var txt = joOutput(templateObject);
document.write(txt);

JavaScript -- find matching object in array of objects

I am trying to search for an object in an array of objects.
Note, vals and recs objects will be DYNAMIC.
var vals = {ID: "4", LOC: "LA", SEQ: "1"};
var recs = [
{ID:"4", LOC:"LA", SEQ:"1"},
{ID:"4", LOC:"NY", SEQ:"1"},
{ID:"4", LOC:"CHI",SEQ:"1"}
];
Now I need to check if all key:value pairs in vals already exist in recs . In this case, recs[0] is an exact match of vals.
Heres my attempt:
var vals = {ID: "4", LOC: "LA", SEQ: "1"};
var recs = [
{ID:"4", LOC:"LA", SEQ:"1"},
{ID:"3", LOC:"NY", SEQ:"2"},
{ID:"2", LOC:"CHI",SEQ:"3"}
];
for(var i = 0; i<recs.length; i++){
if(recs[i]["ID"] == vals["ID"] && recs[i]["LOC"] == vals["LOC"] && recs[i]["SEQ"] == vals["SEQ"]){
console.log(true);
}
else{
console.log(false);
}
}
The above works only because I have hardcoded the keys from the vals object. In reality, the VALS object (and recs) will be DYNAMIC with X number of key:value pairs.
So how can I modify my for loop for a dynamic vals object?
thanks!
Try this:
for (var i = 0; i < recs.length; i++) {
var found = true;
for (var p in vals) {
if (vals.hasOwnProperty(p)) {
if (recs[i][p] !== vals[p]) {
found = false;
break;
}
}
}
console.log(found);
}
for(var i = 0; i<recs.length; i++) {
for (var prop in object) {
if (recs[i][prop] != vals[prop]) {
console.log(false);
return;
}
}
//check from both sides
for (var prop in vals) {
if (recs[i][prop] != vals[prop]) {
console.log(false);
return;
}
}
console.log(true);
}
You could iterate over the keys; something along the lines of:
var vals = { ID: "4", LOC: "LA", SEQ: "1", REGION: "USA" };
var recs = [{ ID: 4, LOC: "LA", SEQ: "1", REGION: "USA" },
{ ID: 3, LOC: "NY", SEQ: "2", REGION: "USA" },
{ ID: 2, LOC: "CHI", SEQ: "3", REGION: "USA" }
];
var isSame = true;
for (var i = 0; i < recs.length; i++) {
console.log( i + '----------------' );
var isSame = true;
// get the keys of the record
var keys = Object.keys(recs[i]);
for (var j = 0; j < keys.length; j++) {
var key = keys[j];
var record = recs[i]
console.log( key + ": " + record[key] + '=' + vals[key] );
if (record[key] != vals[key] ) {
isSame = false;// not equal
break;
}
}
console.log('isSame: ' + isSame );
console.log('------------------' );
}
You need to break it into two loops, one for each object of the array and one for each key of the object:
for(var i = 0; i<recs.length; i++){
var found = false
for(var key in recs[i]) {
if(recs[i].hasOwnProperty(key)){
if(recs[i][key] != vals[key]){
found = true
}
}
console.log(found)
}
the hasOwnProperty call will make sure it doesn't break if the object does not have that key.
You can try this:
function myFind(recs, vals) {
return recs.some(function(obj) {
for (var x in obj)
if (x in vals && obj[x] != vals[x])
return false;
return true;
});
}
var recs = [
{ID:4, LOC:"LA", SEQ:"1", USA:"USA"},
{ID:3, LOC:"NY", SEQ:"2", USA:"USA"},
{ID:2, LOC:"CHI",SEQ:"3", USA:"USA"}
];
var vals = {ID: "4", LOC: "LA", SEQ: "1"};
if (myFind(recs, vals)) {
alert('found');
} else {
alert('not found');
}
Hope it helps.
you can use underscorejs isEqual for this kind of problem

Extract variables from formatted string

I'm trying to see if there's something that exists to do the following. I'd hate to reinvent the wheel.
I have a string like this. It's a file name.
2014-12-22-thomas-javascript.md
I want to be able to provide a formatĀ for this schema.
{year}-{month}-{day}-{name}-{topic}.{extension}
And I'd like an object in return.
{
"year": "2014",
"month": "12",
"day": "22",
"name": "thomas",
"topic": "javascript",
"extension": "md"
}
This way of providing a string and a format is very reticent of these two functions from node core util, moment parse, and jekyll post names.
moment("12-25-1995", "MM-DD-YYYY");
util.format(format, [...])
jeykll post names
At the risk of reinventing a wheel;
String.parse = function parse() {
if(typeof arguments[0] === 'string' && typeof arguments[1] === 'string' ) {
var str = arguments[0];
var val = arguments[1];
var array = [], obj = {};
var re = /(?:{)([a-z]*)(?:})/gi;
var match, sre = str;
while(match = re.exec(str)) {
array.push(match[1]);
sre = sre.replace(match[0], '(\\w*)');
}
re = new RegExp(sre);
var matches = val.match(re);
if(matches) {
for(var i = 1; i < matches.length; i++) {
obj[array[i-1]] = matches[i];
}
}
return obj;
}
}
No doubt there are a bunch of ways this will break but works with your example;
String.parse("{year}-{month}-{day}-{name}-{topic}.{extension}", "2014-12-22-thomas-javascript.md");
EDIT
And for performing the reverse;
String.format = function format() {
if (typeof arguments[0] === 'string') {
var s = arguments[0];
var re = /(?:{)(\d*)(?:})/gi;
var a, i, match, search = s;
while (match = re.exec(search)) {
for (i = 1; i < arguments.length; i++) {
a = parseInt(match[1]) + 1;
s = s.replace(match[0], typeof arguments[a] !== 'object' ? arguments[a] || '' : '');
}
}
re = /(?:{)([a-z]*)(?:})/gi;
match, search = s;
while (match = re.exec(search)) {
for (i = 1; i < arguments.length; i++) {
if (arguments[i].hasOwnProperty(match[1])) {
s = s.replace(match[0], arguments[i][match[1]]);
}
}
}
return s;
}
}
Which can accept named object members or positional for non-objects.
String.format("{0}-{name}{1}-{year}-{src}", 20, "abc", { name: "xyz", year: 2014 }, { src: 'StackOverflow' });
20-xyzabc-2014-StackOverflow
I found the code in Hexo, it's like jekyll but built with node.
var Permalink = require('hexo-util').Permalink;
var permalink = new Permalink(':year-:month-:day-:name-:topic.:extension
', {
segments: {
year: /(\d{4})/,
month: /(\d{2})/,
day: /(\d{2})/
}
});
permalink.parse('2014-12-22-thomas-javascript.md');
// {year: 2014, month: 12, day: 22, ...}

JSON.stringify whitelisting with nested objects

Given following example:
var test = {
"company_name": "Foobar",
"example": "HelloWorld",
"address": {
"street": "My Street 12",
"example": "BarFoo",
"details": "Berlin",
}
}
console.log(JSON.stringify(test, ['company_name','address','street','example']));
// What I actually want
// console.log(JSON.stringify(test, ['company_name','address.street','address.example']));
How can I use JSON's stringify function to deal with nested objects properly?
Since I have huge JSON objects it happens that a key of a nested object is identical to it's "parent" object. I would like to specify my whitelist more granulary.
If you're willing to go to the effort of whitelisting, then you can establish an array of valid keys, which can provide the ability to nest similar to how many systems do JSON nesting (a . separator, or any separator of your choosing).
var whitelistedObj = whitelistJson(obj, ["company_name", "example", "address.street", "address.example"]);
function whitelistJson(obj, whitelist, separator) {
var object = {};
for (var i = 0, length = whitelist.length; i < length; ++i) {
var k = 0,
names = whitelist[i].split(separator || '.'),
value = obj,
name,
count = names.length - 1,
ref = object,
exists = true;
// fill in any empty objects from first name to end without
// picking up neighboring fields
while (k < count) { // walks to n - 1
name = names[k++];
value = value[name];
if (typeof value !== 'undefined') {
if (typeof object[name] === 'undefined') {
ref[name] = {};
}
ref = ref[name];
}
else {
exists = false;
break;
}
}
if (exists) {
ref[names[count]] = value[names[count]];
}
}
return object;
}
I have a JSFiddle showing its usage as well (to ensure it actually worked on my admittedly small sample set).
You can add toJSON method in your huge JSON objects:
var test = {
"company_name": "Foobar",
"example": "HelloWorld",
"address": {
"street": "My Street 12",
"example": "BarFoo",
"details": "Berlin",
},
toJSON: function () {
return {
company_name: this.company_name,
address: {
street: this.address.street,
example: this.address.example
}
}
}
}
And, you get:
console.log(JSON.stringify(test)); // "{"company_name":"Foobar","address":{"street":"My Street 12","example":"BarFoo"}}"
Or, you can use some filter function: (this function using lodash)
function filter(object, keys, sep) {
sep = sep || '.';
var result = {};
_.each(keys, function (key) {
var keyParts = key.split(sep),
res = object,
branch = {},
branchPart = branch;
for (var i = 0; i < keyParts.length; i++) {
key = keyParts[i];
if (!_.has(res, key)) {
return;
}
branchPart[key] = _.isObject(res[key]) ? {} : res[key];
branchPart = branchPart[key];
res = res[key];
}
_.merge(result, branch);
});
return result;
}
console.log(JSON.stringify(filter(test, ['company_name', 'address.street', 'address.example']))); // "{"company_name":"Foobar","address":{"street":"My Street 12","example":"BarFoo"}}"
Check out jsfiddle http://jsfiddle.net/SaKhG/

Categories

Resources