Related
I found this JavaScript code for copying Objects, the code is doing what it's suppose to do, but what I don't understand is when the function call itself; how come newObject in the first iteration desn't loose its value, it should be overwritten when the function called itself and created a new newObject ? does that mean that when a function call itself it still keeps a copy of the first newObject created before it called itself?
const o = {
a: 'a',
b: 'b',
obj: {
key: 'key',
},
}
const o2 = o
o2.a = 'new value'
// o and o2 reference the same object
console.log(o.a)
// this shallow-copies o into o3
const o3 = Object.assign({}, o)
// deep copy
function deepCopy(obj) {
// check if vals are objects
// if so, copy that object (deep copy)
// else return the value
const keys = Object.keys(obj)
const newObject = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
if (typeof obj[key] === 'object') {
newObject[key] = deepCopy(obj[key])
} else {
newObject[key] = obj[key]
}
}
return newObject
}
const o4 = deepCopy(o)
o.obj.key = 'new key!'
console.log(o4.obj.key)
Recursive functions can be confusing. A few well placed console.log()s or running the code in a debugger can really help. The function makes a newObject for the original object and each child object in the object. As the recursion unwinds it sets the property in the parent to the result of the recursive call on the child.
You can see the effect in the console.logs here:
const o = {
a: 'a',
b: 'b',
obj: {
key: 'key',
deeper: {one: 1, two: 2}
},
}
// deep copy
function deepCopy(obj) {
console.log("deep copy of: ", obj)
const keys = Object.keys(obj)
const newObject = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
if (typeof obj[key] === 'object') {
console.log("setting child of", key, "to:")
newObject[key] = deepCopy(obj[key])
} else {
newObject[key] = obj[key]
}
}
return newObject
}
console.log("starting with")
const o4 = deepCopy(o)
Each of the lines starting with deep copy of indicates a newly created newObject in a recursive call, but the only newObject returned is the first one — all the others get set as children.
I would like some advice to return the properties of an object where others are deleted using lodash.
Example :
deletePropsThatStarsWith({a:1, b:2, aa:3}, 'a') //ouputs {b:2}
So far this is what is working but i don't know where i need to go.
var obj = { a: 1, b: 2, aa: 3 }
function deletePropsThatStartsWith(object, prop) {
return _.omit(object, prop)
}
console.log(deletePropsThatStartsWith(obj, 'a'))
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.10/lodash.min.js"></script>
Thanks for help/advices.
You're looking for the lodash _.omitBy method. You can pass a callback for the second parameter, which decides which properties to omit. The callback will be invoked with two arguments, value and key.
So for your example, all you need is to check the key, and see if it starts with the desired string. The .startsWith method will be a good choice, or if you can't use that, lodash itself provides a _.startsWith method.
Example:
var obj = {a: 1, b: 2, aa: 3}
function deletePropsThatStartsWith(object, prop) {
return _.omitBy(object, (v, k) => k.startsWith(prop))
}
console.log(deletePropsThatStartsWith(obj, 'a'))
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.10/lodash.min.js"></script>
You don't need lodash for this, try to implement it by yourself:
function deletePropsThatStartsWith(object, letter) {
var newObjc = {}
Object.keys(object).forEach((key) => {
if (!key.startsWith(letter)) {
newObjc = { ...newObjc, [key]: object[key] }
}
})
return newObjc
}
const objc = { a: 1, b: 2, aa: 3 }
console.log(deletePropsThatStartsWith(objc, 'a'))
The output is { b: 2 }
Alternatively, you can get the object keys and not use lodash at all. This is built into vanilla js (my favorite JS framework!)
function deletePropsThatStartsWith(oldObject, prop) {
let newObject = {};
Object.keys(oldObject).forEach(k => {
if (k.startsWith(prop)) continue;
newObject[k] = oldObject[k];
});
return newObject;
};
// without lodash
function deletePropsThatStartsWith(obj, prop) {
var keys = Object.keys(obj);
var newObj = {};
for(var i = 0;i < keys.length;i++){
if(keys[i][0] !== prop){
newObj[keys[i]] = obj[keys[i]]
}
}
return newObj;
}
console.log(deletePropsThatStartsWith({a:1,b:2,aa:3},'a'));
You can do this with a simple for..in loop:
function deletePropsThatStartsWith(object, prop) {
let copy = {};
for (key in object) {
if (!key.startsWith(prop))
copy[key] = object[key]
};
return copy;
}
var obj = { a: 1, b: 2, aa: 3 }
console.log(deletePropsThatStartsWith(obj, "a"));
This question already has answers here:
How can I get query string values in JavaScript?
(73 answers)
Closed 1 year ago.
I have a string like this:
abc=foo&def=%5Basf%5D&xyz=5
How can I convert it into a JavaScript object like this?
{
abc: 'foo',
def: '[asf]',
xyz: 5
}
In the year 2021... Please consider this obsolete.
Edit
This edit improves and explains the answer based on the comments.
var search = location.search.substring(1);
JSON.parse('{"' + decodeURI(search).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g,'":"') + '"}')
Example
Parse abc=foo&def=%5Basf%5D&xyz=5 in five steps:
decodeURI: abc=foo&def=[asf]&xyz=5
Escape quotes: same, as there are no quotes
Replace &: abc=foo","def=[asf]","xyz=5
Replace =: abc":"foo","def":"[asf]","xyz":"5
Suround with curlies and quotes: {"abc":"foo","def":"[asf]","xyz":"5"}
which is legal JSON.
An improved solution allows for more characters in the search string. It uses a reviver function for URI decoding:
var search = location.search.substring(1);
JSON.parse('{"' + search.replace(/&/g, '","').replace(/=/g,'":"') + '"}', function(key, value) { return key===""?value:decodeURIComponent(value) })
Example
search = "abc=foo&def=%5Basf%5D&xyz=5&foo=b%3Dar";
gives
Object {abc: "foo", def: "[asf]", xyz: "5", foo: "b=ar"}
Original answer
A one-liner:
JSON.parse('{"' + decodeURI("abc=foo&def=%5Basf%5D&xyz=5".replace(/&/g, "\",\"").replace(/=/g,"\":\"")) + '"}')
2022 ES6/7/8 and on approach
Starting ES6 and on, Javascript offers several constructs in order to create a performant solution for this issue.
This includes using URLSearchParams and iterators
let params = new URLSearchParams('abc=foo&def=%5Basf%5D&xyz=5');
params.get("abc"); // "foo"
Should your use case requires you to actually convert it to object, you can implement the following function:
function paramsToObject(entries) {
const result = {}
for(const [key, value] of entries) { // each 'entry' is a [key, value] tupple
result[key] = value;
}
return result;
}
Basic Demo
const urlParams = new URLSearchParams('abc=foo&def=%5Basf%5D&xyz=5');
const entries = urlParams.entries(); //returns an iterator of decoded [key,value] tuples
const params = paramsToObject(entries); //{abc:"foo",def:"[asf]",xyz:"5"}
Using Object.fromEntries and spread
We can use Object.fromEntries, replacing paramsToObject with Object.fromEntries(entries).
The value pairs to iterate over are the list name-value pairs with the
key being the name and the value being the value.
Since URLParams, returns an iterable object, using the spread operator instead of calling .entries will also yield entries per its spec:
const urlParams = new URLSearchParams('abc=foo&def=%5Basf%5D&xyz=5');
const params = Object.fromEntries(urlParams); // {abc: "foo", def: "[asf]", xyz: "5"}
Note: All values are automatically strings as per the URLSearchParams spec
Multiple same keys
As #siipe pointed out, strings containing multiple same-key values will be coerced into the last available value: foo=first_value&foo=second_value will in essence become: {foo: "second_value"}.
As per this answer: https://stackoverflow.com/a/1746566/1194694 there's no spec for deciding what to do with it and each framework can behave differently.
A common use case will be to join the two same values into an array, making the output object into:
{foo: ["first_value", "second_value"]}
This can be achieved with the following code:
const groupParamsByKey = (params) => [...params.entries()].reduce((acc, tuple) => {
// getting the key and value from each tuple
const [key, val] = tuple;
if(acc.hasOwnProperty(key)) {
// if the current key is already an array, we'll add the value to it
if(Array.isArray(acc[key])) {
acc[key] = [...acc[key], val]
} else {
// if it's not an array, but contains a value, we'll convert it into an array
// and add the current value to it
acc[key] = [acc[key], val];
}
} else {
// plain assignment if no special case is present
acc[key] = val;
}
return acc;
}, {});
const params = new URLSearchParams('abc=foo&def=%5Basf%5D&xyz=5&def=dude');
const output = groupParamsByKey(params) // {abc: "foo", def: ["[asf]", "dude"], xyz: 5}
One liner. Clean and simple.
const params = Object.fromEntries(new URLSearchParams(location.search));
For your specific case, it would be:
const str = 'abc=foo&def=%5Basf%5D&xyz=5';
const params = Object.fromEntries(new URLSearchParams(str));
console.log(params);
2023 One-Liner Approach
For the general case where you want to parse query params to an object:
Object.fromEntries(new URLSearchParams(location.search));
For your specific case:
Object.fromEntries(new URLSearchParams('abc=foo&def=%5Basf%5D&xyz=5'));
Split on & to get name/value pairs, then split each pair on =. Here's an example:
var str = "abc=foo&def=%5Basf%5D&xy%5Bz=5"
var obj = str.split("&").reduce(function(prev, curr, i, arr) {
var p = curr.split("=");
prev[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
return prev;
}, {});
Another approach, using regular expressions:
var obj = {};
str.replace(/([^=&]+)=([^&]*)/g, function(m, key, value) {
obj[decodeURIComponent(key)] = decodeURIComponent(value);
});
This is adapted from John Resig's "Search and Don’t Replace".
The proposed solutions I found so far do not cover more complex scenarios.
I needed to convert a query string like
https://random.url.com?Target=Offer&Method=findAll&filters%5Bhas_goals_enabled%5D%5BTRUE%5D=1&filters%5Bstatus%5D=active&fields%5B%5D=id&fields%5B%5D=name&fields%5B%5D=default_goal_name
into an object like:
{
"Target": "Offer",
"Method": "findAll",
"fields": [
"id",
"name",
"default_goal_name"
],
"filters": {
"has_goals_enabled": {
"TRUE": "1"
},
"status": "active"
}
}
OR:
https://random.url.com?Target=Report&Method=getStats&fields%5B%5D=Offer.name&fields%5B%5D=Advertiser.company&fields%5B%5D=Stat.clicks&fields%5B%5D=Stat.conversions&fields%5B%5D=Stat.cpa&fields%5B%5D=Stat.payout&fields%5B%5D=Stat.date&fields%5B%5D=Stat.offer_id&fields%5B%5D=Affiliate.company&groups%5B%5D=Stat.offer_id&groups%5B%5D=Stat.date&filters%5BStat.affiliate_id%5D%5Bconditional%5D=EQUAL_TO&filters%5BStat.affiliate_id%5D%5Bvalues%5D=1831&limit=9999
INTO:
{
"Target": "Report",
"Method": "getStats",
"fields": [
"Offer.name",
"Advertiser.company",
"Stat.clicks",
"Stat.conversions",
"Stat.cpa",
"Stat.payout",
"Stat.date",
"Stat.offer_id",
"Affiliate.company"
],
"groups": [
"Stat.offer_id",
"Stat.date"
],
"limit": "9999",
"filters": {
"Stat.affiliate_id": {
"conditional": "EQUAL_TO",
"values": "1831"
}
}
}
I compiled and adapted multiple solutions into one that actually works:
CODE:
var getParamsAsObject = function (query) {
query = query.substring(query.indexOf('?') + 1);
var re = /([^&=]+)=?([^&]*)/g;
var decodeRE = /\+/g;
var decode = function (str) {
return decodeURIComponent(str.replace(decodeRE, " "));
};
var params = {}, e;
while (e = re.exec(query)) {
var k = decode(e[1]), v = decode(e[2]);
if (k.substring(k.length - 2) === '[]') {
k = k.substring(0, k.length - 2);
(params[k] || (params[k] = [])).push(v);
}
else params[k] = v;
}
var assign = function (obj, keyPath, value) {
var lastKeyIndex = keyPath.length - 1;
for (var i = 0; i < lastKeyIndex; ++i) {
var key = keyPath[i];
if (!(key in obj))
obj[key] = {}
obj = obj[key];
}
obj[keyPath[lastKeyIndex]] = value;
}
for (var prop in params) {
var structure = prop.split('[');
if (structure.length > 1) {
var levels = [];
structure.forEach(function (item, i) {
var key = item.replace(/[?[\]\\ ]/g, '');
levels.push(key);
});
assign(params, levels, params[prop]);
delete(params[prop]);
}
}
return params;
};
A concise solution:
location.search
.slice(1)
.split('&')
.map(p => p.split('='))
.reduce((obj, pair) => {
const [key, value] = pair.map(decodeURIComponent);
obj[key] = value;
return obj;
}, {});
This is the simple version, obviously you'll want to add some error checking:
var obj = {};
var pairs = queryString.split('&');
for(i in pairs){
var split = pairs[i].split('=');
obj[decodeURIComponent(split[0])] = decodeURIComponent(split[1]);
}
For Node JS, you can use the Node JS API querystring:
const querystring = require('querystring');
querystring.parse('abc=foo&def=%5Basf%5D&xyz=5&foo=b%3Dar');
// returns the object
Documentation: https://nodejs.org/api/querystring.html
I found $.String.deparam the most complete pre built solution (can do nested objects etc.). Check out the documentation.
Another solution based on the latest standard of URLSearchParams (https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)
function getQueryParamsObject() {
const searchParams = new URLSearchParams(location.search.slice(1));
return searchParams
? _.fromPairs(Array.from(searchParams.entries()))
: {};
}
Please note that this solution is making use of
Array.from (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from)
and _.fromPairs (https://lodash.com/docs#fromPairs) of lodash for the sake of simplicity.
It should be easy to create a more compatible solution since you have access to searchParams.entries() iterator.
I had the same problem, tried the solutions here, but none of them really worked, since I had arrays in the URL parameters, like this:
?param[]=5¶m[]=8&othr_param=abc¶m[]=string
So I ended up writing my own JS function, which makes an array out of the param in URI:
/**
* Creates an object from URL encoded data
*/
var createObjFromURI = function() {
var uri = decodeURI(location.search.substr(1));
var chunks = uri.split('&');
var params = Object();
for (var i=0; i < chunks.length ; i++) {
var chunk = chunks[i].split('=');
if(chunk[0].search("\\[\\]") !== -1) {
if( typeof params[chunk[0]] === 'undefined' ) {
params[chunk[0]] = [chunk[1]];
} else {
params[chunk[0]].push(chunk[1]);
}
} else {
params[chunk[0]] = chunk[1];
}
}
return params;
}
One of the simplest way to do this using URLSearchParam interface.
Below is the working code snippet:
let paramObj={},
querystring=window.location.search,
searchParams = new URLSearchParams(querystring);
//*** :loop to add key and values to the param object.
searchParams.forEach(function(value, key) {
paramObj[key] = value;
});
There is quite simple and incorrect answer with ES6:
console.log(
Object.fromEntries(new URLSearchParams(`abc=foo&def=%5Basf%5D&xyz=5`))
);
But this one line code do not cover multiple same keys, you have to use something more complicated:
function parseParams(params) {
const output = [];
const searchParams = new URLSearchParams(params);
// Set will return only unique keys()
new Set([...searchParams.keys()])
.forEach(key => {
output[key] = searchParams.getAll(key).length > 1 ?
searchParams.getAll(key) : // get multiple values
searchParams.get(key); // get single value
});
return output;
}
console.log(
parseParams('abc=foo&cars=Ford&cars=BMW&cars=Skoda&cars=Mercedes')
)
Code will generate follow structure:
[
abc: "foo"
cars: ["Ford", "BMW", "Skoda", "Mercedes"]
]
Using ES6, URL API and URLSearchParams API.
function objectifyQueryString(url) {
let _url = new URL(url);
let _params = new URLSearchParams(_url.search);
let query = Array.from(_params.keys()).reduce((sum, value)=>{
return Object.assign({[value]: _params.get(value)}, sum);
}, {});
return query;
}
ES6 one liner (if we can call it that way seeing the long line)
[...new URLSearchParams(location.search).entries()].reduce((prev, [key,val]) => {prev[key] = val; return prev}, {})
One simple answer with build in native Node module.(No third party npm modules)
The querystring module provides utilities for parsing and formatting URL query strings. It can be accessed using:
const querystring = require('querystring');
const body = "abc=foo&def=%5Basf%5D&xyz=5"
const parseJSON = querystring.parse(body);
console.log(parseJSON);
Pretty easy using the URLSearchParams JavaScript Web API,
var paramsString = "abc=foo&def=%5Basf%5D&xyz=5";
//returns an iterator object
var searchParams = new URLSearchParams(paramsString);
//Usage
for (let p of searchParams) {
console.log(p);
}
//Get the query strings
console.log(searchParams.toString());
//You can also pass in objects
var paramsObject = {abc:"forum",def:"%5Basf%5D",xyz:"5"}
//returns an iterator object
var searchParams = new URLSearchParams(paramsObject);
//Usage
for (let p of searchParams) {
console.log(p);
}
//Get the query strings
console.log(searchParams.toString());
##Useful Links
URLSearchParams - Web APIs | MDN
Easy URL Manipulation with URLSearchParams | Web
| Google Developers
NOTE: Not Supported in IE
There is no native solution that I'm aware of. Dojo has a built-in unserialization method if you use that framework by chance.
Otherwise you can implement it yourself rather simply:
function unserialize(str) {
str = decodeURIComponent(str);
var chunks = str.split('&'),
obj = {};
for(var c=0; c < chunks.length; c++) {
var split = chunks[c].split('=', 2);
obj[split[0]] = split[1];
}
return obj;
}
edit: added decodeURIComponent()
/**
* Parses and builds Object of URL query string.
* #param {string} query The URL query string.
* #return {!Object<string, string>}
*/
function parseQueryString(query) {
if (!query) {
return {};
}
return (/^[?#]/.test(query) ? query.slice(1) : query)
.split('&')
.reduce((params, param) => {
const item = param.split('=');
const key = decodeURIComponent(item[0] || '');
const value = decodeURIComponent(item[1] || '');
if (key) {
params[key] = value;
}
return params;
}, {});
}
console.log(parseQueryString('?v=MFa9pvnVe0w&ku=user&from=89&aw=1'))
see log
There's a lightweight library called YouAreI.js that's tested and makes this really easy.
YouAreI = require('YouAreI')
uri = new YouAreI('http://user:pass#www.example.com:3000/a/b/c?d=dad&e=1&f=12.3#fragment');
uri.query_get() => { d: 'dad', e: '1', f: '12.3' }
If you are using URI.js, you can use:
https://medialize.github.io/URI.js/docs.html#static-parseQuery
var result = URI.parseQuery("?foo=bar&hello=world&hello=mars&bam=&yup");
result === {
foo: "bar",
hello: ["world", "mars"],
bam: "",
yup: null
};
console.log(decodeURI('abc=foo&def=%5Basf%5D&xyz=5')
.split('&')
.reduce((result, current) => {
const [key, value] = current.split('=');
result[key] = value;
return result
}, {}))
This seems to be the best solution as it takes multiple parameters of the same name into consideration.
function paramsToJSON(str) {
var pairs = str.split('&');
var result = {};
pairs.forEach(function(pair) {
pair = pair.split('=');
var name = pair[0]
var value = pair[1]
if( name.length )
if (result[name] !== undefined) {
if (!result[name].push) {
result[name] = [result[name]];
}
result[name].push(value || '');
} else {
result[name] = value || '';
}
});
return( result );
}
something
paramsToJSON("x=1&x=2&x=3&y=blah");
console yields => {x: Array[3], y: "blah"} where x is an array as is proper JSON
I later decided to convert it to a jQuery plugin too...
$.fn.serializeURLParams = function() {
var result = {};
if( !this.is("a") || this.attr("href").indexOf("?") == -1 )
return( result );
var pairs = this.attr("href").split("?")[1].split('&');
pairs.forEach(function(pair) {
pair = pair.split('=');
var name = decodeURI(pair[0])
var value = decodeURI(pair[1])
if( name.length )
if (result[name] !== undefined) {
if (!result[name].push) {
result[name] = [result[name]];
}
result[name].push(value || '');
} else {
result[name] = value || '';
}
});
return( result )
}
something
$("a").serializeURLParams();
console yields => {x: Array[3], y: "blah"} where x is an array as is proper JSON
Now, the first will accept the parameters only but the jQuery plugin will take the whole url and return the serialized parameters.
Here's one I use:
var params = {};
window.location.search.substring(1).split('&').forEach(function(pair) {
pair = pair.split('=');
if (pair[1] !== undefined) {
var key = decodeURIComponent(pair[0]),
val = decodeURIComponent(pair[1]),
val = val ? val.replace(/\++/g,' ').trim() : '';
if (key.length === 0) {
return;
}
if (params[key] === undefined) {
params[key] = val;
}
else {
if ("function" !== typeof params[key].push) {
params[key] = [params[key]];
}
params[key].push(val);
}
}
});
console.log(params);
Basic usage, eg.
?a=aa&b=bb
Object {a: "aa", b: "bb"}
Duplicate params, eg.
?a=aa&b=bb&c=cc&c=potato
Object {a: "aa", b: "bb", c: ["cc","potato"]}
Missing keys, eg.
?a=aa&b=bb&=cc
Object {a: "aa", b: "bb"}
Missing values, eg.
?a=aa&b=bb&c
Object {a: "aa", b: "bb"}
The above JSON/regex solutions throw a syntax error on this wacky url:
?a=aa&b=bb&c=&=dd&e
Object {a: "aa", b: "bb", c: ""}
Here's my quick and dirty version, basically its splitting up the URL parameters separated by '&' into array elements, and then iterates over that array adding key/value pairs separated by '=' into an object. I'm using decodeURIComponent() to translate the encoded characters to their normal string equivalents (so %20 becomes a space, %26 becomes '&', etc):
function deparam(paramStr) {
let paramArr = paramStr.split('&');
let paramObj = {};
paramArr.forEach(e=>{
let param = e.split('=');
paramObj[param[0]] = decodeURIComponent(param[1]);
});
return paramObj;
}
example:
deparam('abc=foo&def=%5Basf%5D&xyz=5')
returns
{
abc: "foo"
def:"[asf]"
xyz :"5"
}
The only issue is that xyz is a string and not a number (due to using decodeURIComponent()), but beyond that its not a bad starting point.
//under ES6
const getUrlParamAsObject = (url = window.location.href) => {
let searchParams = url.split('?')[1];
const result = {};
//in case the queryString is empty
if (searchParams!==undefined) {
const paramParts = searchParams.split('&');
for(let part of paramParts) {
let paramValuePair = part.split('=');
//exclude the case when the param has no value
if(paramValuePair.length===2) {
result[paramValuePair[0]] = decodeURIComponent(paramValuePair[1]);
}
}
}
return result;
}
If you need recursion, you can use the tiny js-extension-ling library.
npm i js-extension-ling
const jsx = require("js-extension-ling");
console.log(jsx.queryStringToObject("a=1"));
console.log(jsx.queryStringToObject("a=1&a=3"));
console.log(jsx.queryStringToObject("a[]=1"));
console.log(jsx.queryStringToObject("a[]=1&a[]=pomme"));
console.log(jsx.queryStringToObject("a[0]=one&a[1]=five"));
console.log(jsx.queryStringToObject("http://blabla?foo=bar&number=1234"));
console.log(jsx.queryStringToObject("a[fruits][red][]=strawberry"));
console.log(jsx.queryStringToObject("a[fruits][red][]=strawberry&a[1]=five&a[fruits][red][]=cherry&a[fruits][yellow][]=lemon&a[fruits][yellow][688]=banana"));
This will output something like this:
{ a: '1' }
{ a: '3' }
{ a: { '0': '1' } }
{ a: { '0': '1', '1': 'pomme' } }
{ a: { '0': 'one', '1': 'five' } }
{ foo: 'bar', number: '1234' }
{
a: { fruits: { red: { '0': 'strawberry' } } }
}
{
a: {
'1': 'five',
fruits: {
red: { '0': 'strawberry', '1': 'cherry' },
yellow: { '0': 'lemon', '688': 'banana' }
}
}
}
Note: it's based on locutus parse_str function (https://locutus.io/php/strings/parse_str/).
FIRST U NEED TO DEFINE WHAT'S A GET VAR:
function getVar()
{
this.length = 0;
this.keys = [];
this.push = function(key, value)
{
if(key=="") key = this.length++;
this[key] = value;
this.keys.push(key);
return this[key];
}
}
Than just read:
function urlElement()
{
var thisPrototype = window.location;
for(var prototypeI in thisPrototype) this[prototypeI] = thisPrototype[prototypeI];
this.Variables = new getVar();
if(!this.search) return this;
var variables = this.search.replace(/\?/g,'').split('&');
for(var varI=0; varI<variables.length; varI++)
{
var nameval = variables[varI].split('=');
var name = nameval[0].replace(/\]/g,'').split('[');
var pVariable = this.Variables;
for(var nameI=0;nameI<name.length;nameI++)
{
if(name.length-1==nameI) pVariable.push(name[nameI],nameval[1]);
else var pVariable = (typeof pVariable[name[nameI]] != 'object')? pVariable.push(name[nameI],new getVar()) : pVariable[name[nameI]];
}
}
}
and use like:
var mlocation = new urlElement();
mlocation = mlocation.Variables;
for(var key=0;key<mlocation.keys.length;key++)
{
console.log(key);
console.log(mlocation[mlocation.keys[key]];
}
I needed to also deal with + in the query part of the URL (decodeURIComponent doesn't), so I adapted Wolfgang's code to become:
var search = location.search.substring(1);
search = search?JSON.parse('{"' + search.replace(/\+/g, ' ').replace(/&/g, '","').replace(/=/g,'":"') + '"}',
function(key, value) { return key===""?value:decodeURIComponent(value)}):{};
In my case, I'm using jQuery to get URL-ready form parameters, then this trick to build an object out of it and I can then easily update parameters on the object and rebuild the query URL, e.g.:
var objForm = JSON.parse('{"' + $myForm.serialize().replace(/\+/g, ' ').replace(/&/g, '","').replace(/=/g,'":"') + '"}',
function(key, value) { return key===""?value:decodeURIComponent(value)});
objForm.anyParam += stringToAddToTheParam;
var serializedForm = $.param(objForm);
doc = {
'a': {
'b': {
'c': 'hello'
},
'd': {
'c': 'sup',
'e': {
'f': 'blah blah blah'
}
}
}
}
function get(json, path) {
var str = path.split('.');
var temp = json;
var arr = [];
var keystr = "";
for (var i = 0; i < str.length; i++) {
if (str[i] != "*") {
keystr += str[i] + ".";
if (temp[str[i]] === undefined)
break;
else {
temp = temp[str[i]];
if (i == str.length - 1) {
var nObj = {};
nObjKey = keystr.substr(0, keystr.length - 1);
nObj[nObjKey] = temp
// console.log("Obj check" + JSON.stringify(nObj) + keystr)
arr.push(nObj);
}
}
} else {
for (var key in temp) {
var concat = key + "."
for (var j = i + 1; j < str.length; j++)
concat += str[j] + ".";
if (temp[key] !== undefined && temp[key] instanceof Object) {
var m = keystr + concat.substr(0, concat.length - 1);
var obj = (get(temp, concat.substr(0, concat.length - 1)));
if (obj != "") {
// console.log("existing arr "+JSON.stringify(arr))
obj[m] = (obj[0])[concat.substr(0, concat.length - 1)]
// console.log("hello "+JSON.stringify(obj) + " end hello")
arr.push(obj);
}
} else if (temp[key] !== undefined && i == str.length - 1) {
// arr.push(temp);
}
}
}
}
return arr;
}
var result = (get(doc, 'a.*.e'))
console.log(result)
For input of 'a.*.e' the output should be {'a.d.e': {'f': 'blah blah blah'}}}. But I get all the replacement for wild card as well in the array. I am sure something is wrong but not able to detect it. Help would be appreciated.
You could change the structure of the operation a little bit with a recursive approach and an exit early exit often paradigm with checking of single parts with exit options, like
length, a part result is found,
falsy or not object types,
part at index is a star, then iterate all keys from the object, or
the part at index is a key, then call the function again.
At the end, with a found path, joint the path and generate a new property with the actual value of the object.
function get(object, path) {
function iter(o, p, i) {
if (i === parts.length) {
result[p.join('.')] = o;
return;
}
if (!o || typeof o !== 'object') {
return;
}
if (parts[i] === '*') {
Object.keys(o).forEach(function (k) {
iter(o[k], p.concat(k), i + 1);
});
return;
}
if (parts[i] in o) {
iter(o[parts[i]], p.concat(parts[i]), i + 1);
}
}
var result = {},
parts = path.split('.');
iter(object, [], 0);
return result;
}
var doc = { a: { b: { c: 'hello' }, d: { c: 'sup', e: { f: 'blah blah blah' } } } };
console.log(get(doc, 'a.*.e'));
console.log(get(doc, 'a.*.c'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Version with * as wildcard for any level.
function get(object, path) {
function iter(o, p, i) {
if (i === parts.length) {
result[p.join('.')] = o;
return;
}
if (!o || typeof o !== 'object') {
return;
}
if (parts[i] === '*') {
Object.keys(o).forEach(function (k) {
iter(o[k], p.concat(k), i);
iter(o[k], p.concat(k), i + 1);
});
return;
}
if (parts[i] in o) {
iter(o[parts[i]], p.concat(parts[i]), i + 1);
}
}
var result = {},
parts = path.split('.');
iter(object, [], 0);
return result;
}
var doc = { a: { b: { c: 'hello' }, d: { c: 'sup', e: { f: 'blah blah blah' } } } };
console.log(get(doc, 'a.*.e'));
console.log(get(doc, 'a.*.c'));
console.log(get(doc, 'a.*.f'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
First of all, since your desired output {'a.d.e': {'f': 'blah blah blah'}}} does not contain any array, but only plain objects, you should not need the variable arr in your code.
Instead, return nObj as function result, and declare it at the start, never clearing it.
Secondly, when you come back from the recursive call, the results need to be copied while prefixing the paths with what you already had. Note that checking for an empty array should not be done with != "", but anyway, you don't need that any more.
You could write this from scratch in different ways (see solution at end of answer), but I have first adapted your code to only change the bare minimum, with comments where I made the changes to make it work:
function get(json, path) {
var str = path.split('.');
var temp = json;
var arr = [];
var keystr = "";
// *** Define here the object to return
var nObj = {};
for (var i = 0; i < str.length; i++) {
if (str[i] != "*") {
keystr += str[i] + ".";
if (temp[str[i]] === undefined)
break;
else {
temp = temp[str[i]];
if (i == str.length - 1) {
// *** Move this to start of the function
//var nObj = {};
nObjKey = keystr.substr(0, keystr.length - 1);
nObj[nObjKey] = temp
}
}
} else {
for (var key in temp) {
var concat = key + "."
for (var j = i + 1; j < str.length; j++)
concat += str[j] + ".";
if (temp[key] !== undefined && temp[key] instanceof Object) {
var m = keystr + concat.substr(0, concat.length - 1);
var obj = get(temp, concat.substr(0, concat.length - 1));
// *** Return value is object with path(s) as keys
// *** Don't compare array with string
//if (arr != "") {
// *** Iterate over the returned object properties, and prefix them
for (var deepKey in obj) {
nObj[keystr + deepKey] = obj[deepKey];
}
//*** No need for array; we already have the object properties
//arr.push(obj);
//}
// *** No need for array
//} else if (temp[key] !== undefined && i == str.length - 1) {
// arr.push(temp);
}
}
}
}
// *** Return object
return nObj;
}
var doc = {
'a': {
'b': {
'c': 'hello'
},
'd': {
'c': 'sup',
'e': {
'f': 'blah blah blah'
},
},
'g': {
'e': {
'also': 1
}
}
}
}
var result = (get(doc, 'a.*.e'));
console.log(result);
Please also consider not name objects json when they are not: JSON is a text format, JavaScript object variables are not the same thing as JSON.
Compact ES6 solution
When you are used to array functions like reduce and a functional programming style, the following compact ES6 solution might appeal to you:
function get(obj, path) {
if (typeof path === 'string') path = path.split('.');
return !path.length ? { '': obj } // Match
: obj !== Object(obj) ? {} // No match
: (path[0] === '*' ? Object.keys(obj) : [path[0]]) // Candidates
.reduce( (acc, key) => {
const match = get(obj[key], path.slice(1)); // Recurse
return Object.assign(acc, ...Object.keys(match).map( dotKey =>
({ [key + (dotKey ? '.' + dotKey : '')]: match[dotKey] })
));
}, {});
}
const doc = {
'a': {
'b': {
'c': 'hello'
},
'd': {
'c': 'sup',
'e': {
'f': 'blah blah blah'
},
},
'g': {
'e': {
'also': 1
}
}
}
};
const result = get(doc, 'a.*.e');
console.log(result);
List monad
Here's a solution which borrows ideas from the List monad to represent a computation which may have 0, 1, or more results. I'm not going to cover it in detail and I've only included enough of the List type to get a working solution. If you're interested in this sort of approach, you can do some more research on the topic or ask me a follow-up question.
I'm also using an auxiliary find function which is the recursive helper for get which operates the array of keys that get prepares
If you like this solution, I've written about the list monad in some other answers; you might find them helpful ^_^
const List = xs =>
({
value:
xs,
bind: f =>
List (xs.reduce ((acc, x) =>
acc.concat (f (x) .value), []))
})
const find = (path, [key, ...keys], data) =>
{
if (key === undefined)
return List([{ [path.join('.')]: data }])
else if (key === '*')
return List (Object.keys (data)) .bind (k =>
find ([...path, k], keys, data[k]))
else if (data[key] === undefined)
return List ([])
else
return find ([...path, key], keys, data[key])
}
const get = (path, doc) =>
find ([], path.split ('.'), doc) .value
const doc =
{a: {b: {c: 'hello'},d: {c: 'sup',e: {f: 'blah blah blah'}}}}
console.log (get ('a.b.c', doc))
// [ { 'a.b.c': 'hello' } ]
console.log (get ('a.*.c', doc))
// [ { 'a.b.c': 'hello' }, { 'a.d.c': 'sup' } ]
console.log (get ('a.*', doc))
// [ { 'a.b': { c: 'hello' } },
// { 'a.d': { c: 'sup', e: { f: 'blah blah blah' } } } ]
console.log (get ('*.b', doc))
// [ { 'a.b': { c: 'hello' } } ]
Native arrays only
We don't have to do fancy List abstraction in order to achieve the same results. In this version of the code, I'll show you how to do it using nothing but native Arrays. The only disadvantage of this code is the '*'-key branch gets a little complicated by embedding the flatmap code inline with our function
const find = (path, [key, ...keys], data) =>
{
if (key === undefined)
return [{ [path.join ('.')]: data }]
else if (key === '*')
return Object.keys (data) .reduce ((acc, k) =>
acc.concat (find ([...path, k], keys, data[k])), [])
else if (data[key] === undefined)
return []
else
return find ([...path, key], keys, data[key])
}
const get = (path, doc) =>
find([], path.split('.'), doc)
const doc =
{a: {b: {c: 'hello'},d: {c: 'sup',e: {f: 'blah blah blah'}}}}
console.log (get ('a.b.c', doc))
// [ { 'a.b.c': 'hello' } ]
console.log (get ('a.*.c', doc))
// [ { 'a.b.c': 'hello' }, { 'a.d.c': 'sup' } ]
console.log (get ('a.*', doc))
// [ { 'a.b': { c: 'hello' } },
// { 'a.d': { c: 'sup', e: { f: 'blah blah blah' } } } ]
console.log (get ('*.b', doc))
// [ { 'a.b': { c: 'hello' } } ]
Why I recommend the List monad
I do personally recommend the List monad approach as it keeps the body of the find function most clean. It also encompasses the concept of ambiguous computation and allows you to reuse that wherever you might require such a behaviour. Without using the List monad, you would rewrite the necessary code each time which adds a lot of cognitive load on the understanding of the code.
Adjust the shape of your result
The return type of your function is pretty weird. We're returning an Array of objects that only have one key/value pair. The key is the path we found the data on, and the value is the matched data.
In general, we shouldn't be using Object keys this way. How would we display the results of our match?
// get ('a.*', doc) returns
let result =
[ { 'a.b': { c: 'hello' } },
{ 'a.d': { c: 'sup', e: { f: 'blah blah blah' } } } ]
result.forEach (match =>
Object.keys (match) .forEach (path =>
console.log ('path:', path, 'value:', match[path])))
// path: a.b value: { c: 'hello' }
// path: a.d value: { c: 'sup', e: { f: 'blah blah blah' } }
What if we returned [<key>, <value>] instead of {<key>: <value>}? It's much more comfortable to work with a result in this shape. Other reasons to support this is a better shape for your data is something like Array#entries or Map#entries()
// get ('a.*', doc) returns proposed
let result =
[ [ 'a.b', { c: 'hello' } ],
[ 'a.d', { c: 'sup', e: { f: 'blah blah blah' } } ] ]
for (let [path, value] of result)
console.log ('path:', path, 'value:', value)
// path: a.b value: { c: 'hello' }
// path: a.d value: { c: 'sup', e: { f: 'blah blah blah' } }
If you agree this is a better shape, updating the code is simple (changes in bold)
// List monad version
const find = (path, [key, ...keys], data) => {
if (key === undefined)
return List ([[path.join ('.'), data]])
...
}
// native arrays version
const find = (path, [key, ...keys], data) => {
if (key === undefined)
return [[path.join ('.'), data]]
...
}
var arr = { foo : 1, bar: { baz : 2 }, bee : 3 }
function getter(variable) {
return arr[variable];
}
If I want 'foo' vs 'bee' I can just do arr[variable] - that's easy, and the function does that.
But what if I want to get arr.bar.baz AKA arr[bar][baz]?
What can I pass to the getter function that will let me do that, (and of course also let me get non-nested properties using the same function).
I tried getter('bar.baz') and getter('[bar][baz]') but those didn't work.
I suppose I can parse for dots or brackets (like here: In javascript, test for property deeply nested in object graph?). Is there a cleaner way? (Besides eval of course.)
Especially because I need to get the deeply set properly many many times in a loop for a bunch of array elements.
You can use a deep access function based on a string for the path. Note that you can't have any periods in the property names.
function getPropByString(obj, propString) {
if (!propString)
return obj;
var prop, props = propString.split('.');
for (var i = 0, iLen = props.length - 1; i < iLen; i++) {
prop = props[i];
var candidate = obj[prop];
if (candidate !== undefined) {
obj = candidate;
} else {
break;
}
}
return obj[props[i]];
}
var obj = {
foo: {
bar: {
baz: 'x'
}
}
};
console.log(getPropByString(obj, 'foo.bar.baz')); // x
console.log(getPropByString(obj, 'foo.bar.baz.buk')); // undefined
If the access string is empty, returns the object. Otherwise, keeps going along access path until second last accessor. If that's an ojbect, returns the last object[accessor] value. Otherwise, returns undefined.
Using ES6:
var arr = { foo : 1, bar: { baz : 2 }, bee : 3 };
var {foo, bar, bar: {baz}, bee} = arr;
Same as:
// var foo = 1;
// var bar = {baz: 2};
// var baz = 2;
// var bee = 3;
Using lodash:
https://lodash.com/docs#get
_.get(arr, 'bar.baz'); //returns 2;
_.get(arr, 'bar.baz[5].bazzz'); //returns undefined wont throw error;
_.get(arr, 'bar.baz[5].bazzz', 'defaultvalue'); // Returns defaultValue because result is undefined
A recursive way :
function getValue(obj, path) {
if (!path) return obj;
const properties = path.split('.');
return getValue(obj[properties.shift()], properties.join('.'))
}
const myObj = {
foo: {
bar: {
value: 'good'
}
}
}
console.log(getValue(myObj, 'foo.bar.value')); // good
How about change the getter function signature as getter('bar', 'baz') instead
function getter() {
var v = arr;
for(var i=0; i< arguments.length; i++) {
if(!v) return null;
v = v[arguments[i]];
}
return v;
}
ps. didn't test, but you get the idea ;)
A one liner for you:
const mock = {
target: {
"prop1": {
"prop2": {
"prop3": "sad"
}
}
},
path: "prop1.prop2.prop3",
newValue: "happy"
};
mock.path.split(".").reduce(
(acc, curr, i, src) =>
(curr === src[src.length - 1]) ? acc[src[src.length - 1]] = mock.newValue : acc[curr], mock.target);
console.log(mock.target); //? { prop1: { prop2: { prop3: 'happy' } } }
Here's a very simple one liner which grants you dynamic access via "foo.bar.baz" mechanism,
var obj = {
foo: {
bar: {
baz: 'foobarbaz'
}
}
}
const nestedAccess = "foo.bar.baz";
console.log(nestedAccess.split('.').reduce((prev, cur) => prev[cur], obj)) //'foobarbaz'
I have recently developed my own Object method to get an object property nested among objects and arrays regardless how deep it is. It utilizes a single line of recursive approach. Check this out.
Object.prototype.getNestedValue = function(...a) {
return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
var myObj = { foo : 1, bar: { baz : 2 }, bee : 3 },
bazval = myObj.getNestedValue("bar","baz");
document.write(bazval);
Now let's check a deeper nested array object combo data structure
Object.prototype.getNestedValue = function(...a) {
return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
var myArr = [{fox: [{turn:[857, 432]}]}, {sax: [{pana:[777, 987]}]}, {ton: [{joni:[123, 567]}]}, {piu: [{burn:[666, 37]}]}, {sia: [{foxy:[404, 696]}]}];
document.write(myArr.getNestedValue(3,"piu",0,"burn",1));
I believe being able to pass search parameters dynamically to existing array methods would make actions like searching, filtering or replacing of deeply nested structures much easy.
Using reduce we can fetch the value in single line of code.
const testobj = {b:{c:'1', d:{e:'2',f:'3'}}, g:{h:'3'}}
function fetchByDotOperator(object, value) {
return value.split('.').reduce((acc, curr) => acc[curr], object);
}
console.log(fetchByDotOperator(testobj,'b.d.e'))
You can access the functions arguments where you can pass any number of strings.
I also recommend using arr as a parameter for better encapsulation:
function getter() {
var current = arguments[0];
for(var i = 1; i < arguments.length; i++) {
if(current[arguments[i]]) {
current = current[arguments[i]];
} else {
return null;
}
}
return current;
}
var arr = { foo : 1, bar: { baz : 2 }, bee : 3 };
var baz = getter(arr, 'bar', 'baz');
function getPropertyByString(object, propString) {
let value = object;
const props = propString.split('.');
for (let index = 0; index < props.length; index += 1) {
if (props[index] === undefined) break;
value = value[props[index]];
}
return value;
};
const object = {
name: 'any_name',
address: {
number: 77,
test: {
name: 'test'
}
}
}
console.log(getPropertyByString(object, 'address.test.name'))
// test
Above answers help you access nested objects only, however you might also want to access data in an object/array data type. You can try this recusive method:
const getValue = (obj, key) => {
const keyParts = key.split(".");
return getValueHelper(obj, keyParts);
};
const getValueHelper = (obj, keyParts) => {
if (keyParts.length == 0) return obj;
let key = keyParts.shift();
if (Array.isArray(obj[key])) {
return obj[key].map((x) => getValueHelper(x, [...keyParts])).flat();
}
return getValueHelper(obj[key], [...keyParts]);
};
//Examples
let data1 = {
a: [{ b: { c: [{ d: [{ e: 1 }] }] } }, { b: { c: [{ d: [{ e: 2 }] }] } }],
};
console.log(getValue(data1, "a.b.c.d.e"));
//Output
//[ 1, 2 ]
let data2 = {
a:{b:1},
};
console.log(getValue(data2, "a.b"));
//Output
//1
p.s. Remove .flat() to get desired output for arrays.
Theres a function defined on this blog to safely read nested properties from a JS object
It allows you to mine an object for properties... ie.
safeRead(arr, 'foo', 'bar', 'baz');
and if any part of the object chain is null or undefined it returns an empty string....
let obj = {foo : {bar: {baz:1}}};
// -- simply
console.log(eval('obj.foo.bar.baz')); //-- 1
// -- safer
val = "";
try {
val = eval('Obj.foo.bar.baz')
}
catch(e) {
val = "empty"
}
// -- val = 1
// -- use at your risk ;)
Here I created a small suite of functions to 'get / 'set' / 'push' / 'pull' from object nested properties.
inputObject : Target object.
Ex: obj = {a:1, b:{c:2,d:3}}
propertyString : String containing the key to access.
Ex: "b.c"
Finally:
_getObjectValueByPathString(obj, "b.c") would return 2
function _getObjectValueByPathString(inputObject, propertyString) {
let splitStr = propertyString.split('.');
if (!inputObject.hasOwnProperty(splitStr[0])) return undefined;
if (splitStr.length === 1) {
return inputObject[splitStr[0]];
}
else if (splitStr.length > 1) {
let newPropertyString = "";
let firstValue = splitStr.shift();
splitStr.forEach((subStr, i) => {
newPropertyString = i === 0 ? subStr : newPropertyString.concat(`.${subStr}`);
});
return _getObjectValueByPathString(inputObject[firstValue], newPropertyString);
}
else {
throw "Invalid property string provided";
}
}
function _setObjectValueByPathString(inputObject, propertyString, inputValue) {
let splitStr = propertyString.split('.');
if (splitStr.length === 1) {
inputObject[splitStr[0]] = inputValue;
return;
}
else if (splitStr.length > 1) {
let newPropertyString = "";
let firstValue = splitStr.shift();
splitStr.forEach((subStr, i) => {
newPropertyString = i === 0 ? subStr : newPropertyString.concat(`.${subStr}`);
});
_setObjectValueByPathString(inputObject[firstValue], newPropertyString, inputValue);
return;
}
else {
throw "Invalid property string provided";
}
}
function _pushObjectValueByPathString(inputObject, propertyString, inputValue) {
let splitStr = propertyString.split('.');
if (splitStr.length === 1) {
inputObject[splitStr[0]].push(inputValue);
return;
}
else if (splitStr.length > 1) {
let newPropertyString = "";
let firstValue = splitStr.shift();
splitStr.forEach((subStr, i) => {
newPropertyString = i === 0 ? subStr : newPropertyString.concat(`.${subStr}`);
});
_pushObjectValueByPathString(inputObject[firstValue], newPropertyString, inputValue);
return;
}
else {
throw "Invalid property string provided";
}
}
function _pullObjectValueByPathString(inputObject, propertyString, inputValue) {
let splitStr = propertyString.split('.');
if (splitStr.length === 1) {
inputObject[splitStr[0]].pull(inputValue);
return;
}
else if (splitStr.length > 1) {
let newPropertyString = "";
let firstValue = splitStr.shift();
splitStr.forEach((subStr, i) => {
newPropertyString = i === 0 ? subStr : newPropertyString.concat(`.${subStr}`);
});
_pullObjectValueByPathString(inputObject[firstValue], newPropertyString, inputValue);
return;
}
else {
throw "Invalid property string provided";
}
}