Trimming all values in object; Error: Cannot set property of undefined - javascript

I'm receiving this object from an API:
{
"id": 10,
"lectura": "zx71 ",
"articulo": "ZX71 ",
"ubicacion": "ddd ",
"uds": 5,
"usuario": null,
"createD_AT": "2019-12-04T08:19:56.12"
}
I want to trim the values so I have this function:
const trimValues = object => {
console.log(JSON.stringify(object, null, 2))
const trimmedProperties = Object.entries(object).reduce(
(acc, [key, value]) => {
if (typeof value == "string") {
return (acc[key] = value.trim());
}
},
{}
);
return { ...object, trimmedProperties };
};
The problem is that I'm getting this error message:
core.js:9110 ERROR TypeError: Cannot set property 'lectura' of undefined
The error is returned in this line: return (acc[key] = value.trim());
What am I missing?

There are 3 main mistakes first one is that in case of value not being a string you did not return anything meaning that the next iteration of the reduce would receive acc===undefined, and second being that return of the assignment call will just return the value of the key instead of entire object, last being that you also have to destructurize the trimmer properties, you can fix it by doing the following
const trimValues = object => {
console.log(JSON.stringify(object, null, 2))
const trimmedProperties = Object.entries(object).reduce(
(acc, [key, value]) => {
if (typeof value == "string") {
acc[key] = value.trim();
}
return acc
},
{}
);
return { ...object, ...trimmedProperties };
};
however there really isn't a reason you should combine the untrimmed properties (not strings) with trimmed ones only after iterating over it, instead to simplify it and remove unnecessary iterations you could do the following:
const trimValues = object => {
console.log(JSON.stringify(object, null, 2))
return Object.entries(object).reduce((acc, [key, value]) => {
acc[key] = (typeof value === "string") ? value.trim() : value;
return acc
}, {});
};

Related

Find if value is undefined of multiple keys in object

I would like to detect if some values are not defined in an object with several properties
for example :
let test = {
helo: undefined,
hey: "not undefined"
}
i tried this :
const object1 = {
a: 'somestring',
b: 42
};
for (const [key, value] of Object.entries(object1)) {
console.log(`${key}: ${value}`);
}
but if possible, I don't want to use a for loop, I would like a boolean result in return
You could be looking for something like this.
const test = {
helo: undefined,
hey: "not undefined"
};
const some_undefined = Object.values(test).some(v => v === undefined);
console.log(some_undefined);
You can use a function for that :
const hasOneKeyUndefined = (object)=>
{
for (const [key, value] of Object.entries(object)) {
if (value === undefined) return true;
}
return false;
}
You could also use Object.keys
const test = {
helo: undefined,
hey: "not undefined"
};
Object.keys(test).map((key, idx) => test[key] === undefined)

Javascript: recursively encode and decode querystring. (object to querystring, querystring to object)

I want to encode a complex json/javascript object into the standard querystring encoding.
And i want to decode this querystring back to an json/javascript object.
It should be recursively, with arrays, objects, strings, booleans and numbers.
I thought this should be easy, but was proven wrong. Does anyone have an idea, how to solve this problem?
Either in Javascript or preferably in Typescript.
I think, what you want to do, is encode and decode nested objects.
There is no single standard, but very often, QS (Query String) syntax is used:
{
"attribute": "value",
"array": ["apples", "bananas"],
"object": { "number": 55 },
}
will become:
?attribute=value&array[0]=apples&array[1]=bananas&object[number]=55
Example code:
function decode(querystring: string): object {
function parseValue(value: string): any {
if (value === 'TRUE') return true;
if (value === 'FALSE') return false;
return isNaN(Number(value)) ? value : Number(value);
}
function dec(list: any[], isArray = false): object {
let obj: any = isArray ? [] : {};
let recs: any[] = list.filter((item) => {
if (item.keys.length > 1) return true;
obj[item.keys[0]] = parseValue(item.value);
});
let attrs = {};
recs.map((item) => {
item.key = item.keys.shift();
attrs[item.key] = [];
return item;
}).forEach((item) => attrs[item.key].push(item));
Object.keys(attrs).forEach((attr) => {
let nextKey = attrs[attr][0].keys[0];
obj[attr] = dec(attrs[attr], typeof nextKey === 'number');
});
return obj;
}
return dec(
querystring
.split('&')
.map((item) => item.split('=').map((x) => decodeURIComponent(x)))
.map((item) => {
return {
keys: item[0]
.split(/[\[\]]/g)
.filter((n) => n)
.map((key) => (isNaN(Number(key)) ? key : Number(key))),
value: item[1],
};
})
);
}
export function encode(object: object): string {
function reducer(obj, parentPrefix = null) {
return function (prev, key) {
const val = obj[key];
key = encodeURIComponent(key);
const prefix = parentPrefix ? `${parentPrefix}[${key}]` : key;
if (val == null || typeof val === 'function') {
prev.push(`${prefix}=`);
return prev;
}
if (typeof val === 'boolean') {
prev.push(`${prefix}=${val.toString().toUpperCase()}`);
return prev;
}
if (['number', 'string'].includes(typeof val)) {
prev.push(`${prefix}=${encodeURIComponent(val)}`);
return prev;
}
prev.push(
Object.keys(val).reduce(reducer(val, prefix), []).join('&')
);
return prev;
};
}
return Object.keys(object).reduce(reducer(object), []).join('&');
}
const obj = { key: 1, key1: 2, key2: 3 }
const params = encodeURIComponent(JSON.stringify(obj))
window.location.href = `http://example.com/?query=${params}`
end like this:
http://example.com/?query=%7B%22key%22%3A1%2C%22key1%22%3A2%2C%22key2%22%3A3%7D

Making a recursive algorithm for converting URL string into JSON

I found myself having to process a string like:
foo=bar&foo1=foo%3Dbar%26foo2%3Dfoo%253Dbar
Into:
{
"foo": "bar",
"foo1": {
"foo": "bar",
"foo2": {
"foo": "bar"
}
}
}
A real input example.
My best attempt is:
function parse(input) {
try {
const parsed = JSON.parse(input);
return parseJSON(parsed);
} catch (err) {
const decodedInput = decodeURIComponent(input);
if (input.includes("&") && input.includes("=")) {
return input.split("&").reduce((json, part) => {
const [key, value] = part.split("=");
const decodedValue = decodeURIComponent(value);
return { ...json, [key]: parsePrimitive(decodedValue) };
}, {});
}
return decodedInput;
}
}
function parsePrimitive(input) {
if (!isNaN(input)) {
return Number(input);
}
if (input === "true" || input === "false") {
return input === "true";
}
return parse(input);
}
function parseJSON(input) {
return Object.entries(input).reduce((json, [key, value]) => {
let object = {};
if (typeof value === "object") {
if (Array.isArray(value)) {
object[key] = value;
} else {
object[key] = parseJSON(value);
}
} else {
const decodedValue = decodeURIComponent(value);
if (decodedValue.includes("&") && decodedValue.includes("=")) {
object[key] = parse(decodedValue);
} else {
object[key] = parsePrimitive(decodedValue);
}
}
return { ...json, ...object };
}, {});
}
If you try to run it, you're supposed to call parse(input)
However, it does fail for certain inputs
How can I make the perfect recursive algorithm for this kind of problem?
Thanks!
You could take a recursive approach by checking the encoded = sign.
const getValues = string => string.split('&')
.reduce((r, pair) => {
let [key, value] = pair.split('=');
value = decodeURIComponent(value);
r[key] = value.includes('=')
? getValues(value)
: value;
return r;
}, {});
console.log(getValues('foo=bar&foo1=foo%3Dbar%26foo2%3Dfoo%253Dbar'));
This seems to do it for your simple example and your more complex one (now updated to handle numbers and booleans):
const parse = (query) =>
query .startsWith ('{')
? JSON .parse (query)
: query .includes ('&') || query .includes ('=')
? Object .fromEntries (
query .split ('&')
.map (p => p .split ('='))
.map (([k, v]) => [k, parse (decodeURIComponent (v))])
)
: query .includes (',')
? query .split (',') .filter (Boolean) .map (parse)
: isFinite (query)
? Number (query)
: query .toLowerCase () == "true" || query .toLowerCase () == "false"
? query .toLowerCase () == "true"
: // else
query
const q = 'foo=bar&foo1=foo%3Dbar%26foo2%3Dfoo%253Dbar'
console .log (parse(q))
console.log('fetching larger example...')
fetch ('https://gist.githubusercontent.com/avi12/cd1d6728445608d64475809a8ddccc9c/raw/030974baed3eaadb26d9378979b83b1d30a265a3/url-input-example.txt')
.then (res => res .text ())
.then (parse)
.then (console .log)
.as-console-wrapper {max-height: 100% !important; top: 0}
There are two parts that deserve attention.
First, this makes an assumption about commas: that they represent a separation between elements of an array. And, further, it assumes that empty strings aren't intended, turning
watermark=%2Chttps%3A%2F%2Fs.ytimg.com%2Fyts%2Fimg%2Fwatermark%2Fyoutube_watermark-vflHX6b6E.png
%2Chttps%3A%2F%2Fs.ytimg.com%2Fyts%2Fimg%2Fwatermark%2Fyoutube_hd_watermark-vflAzLcD6.png
into this:
watermark: [
"https://s.ytimg.com/yts/img/watermark/youtube_watermark-vflHX6b6E.png",
"https://s.ytimg.com/yts/img/watermark/youtube_hd_watermark-vflAzLcD6.png"
]
The original starts with an encoded comma (%2C), which would lead to an initial empty string, so we use .filter (Boolean) to remove it.
Second, the test for a string representing JSON is very naïve, only doing .startsWith ('{'). You can replace this with whatever you need, but it leads to a question of intentions. I'm not sure we can write this entirely generically in this manner.
Still, I think it's close. And the code is fairly clean.
I do have to wonder why, however. This much data is going to run into various url size limits. At this point, wouldn't putting this into a request body rather than url parameters make much more sense?
Node.js comes with a built-in "querystring" npm package utility, but here I used a better one called "qs". You can specify delimiters in an array instead of only using one with the former.
If you want to use the built-in "querystring" package, you need to remove the delimiter array when calling parse and do a check on the string to see what delimiter is used - the sample file you gave use a few different ones.
So try this:
const qs = require("qs");
let params = `foo=bar&foo1=foo%3Dbar%26foo2%3Dfoo%253Dbar`;
const isObject = (param) => {
try {
let testProp = JSON.parse(param);
if (typeof testProp === "object" && testProp !== null) {
return true;
}
return false;
} catch (e) {
return false;
}
};
const isURL = (value) => {
try {
new URL(value);
} catch (e) {
return false;
}
return true;
};
const isQueryString = (value) => {
if (/[/&=]/.test(value) && !isURL(value)) {
return true;
} else {
return false;
}
};
const parseData = (data, parsed = false) => {
if (isQueryString(data) && !parsed) {
return parseData(qs.parse(data, { delimiter: /[;,/&]/ }), true);
} else if (isObject(data) || parsed) {
for (let propertyName in data) {
if (isObject(data[propertyName])) {
data[propertyName] = parseData(JSON.parse(data[propertyName]), true);
} else {
data[propertyName] = parseData(data[propertyName]);
}
}
return data;
} else {
return data;
}
};
let s = parseData(params);
console.log(JSON.stringify(s, null, 2));
I reworked the algorithm using Object.fromEntries(new URLSearchParams()).
function parse(query) {
try {
return JSON.parse(query);
} catch {
if (!isNaN(query)) {
return Number(query);
}
if (typeof query !== "string") {
const obj = {};
for (const queryKey in query) {
if (query.hasOwnProperty(queryKey)) {
obj[queryKey] = parse(query[queryKey]);
}
}
return obj;
}
if (!query) {
return "";
}
if (query.toLowerCase().match(/^(true|false)$/)) {
return query.toLowerCase() === "true";
}
const object = Object.fromEntries(new URLSearchParams(query));
const values = Object.values(object);
if (values.length === 1 && values[0] === "") {
return query;
}
return parse(object);
}
}
const q = 'foo=bar&foo1=foo%3Dbar%26foo2%3Dfoo%253Dbar';
console.log(parse(q));
console.log('fetching larger example...');
fetch('https://gist.githubusercontent.com/avi12/cd1d6728445608d64475809a8ddccc9c/raw/030974baed3eaadb26d9378979b83b1d30a265a3/url-input-example.txt')
.then(response => response.text())
.then(parse)
.then(console.log);
.as-console-wrapper { max-height: 100% !important; top: 0; }

refactor code that filters based on value of key/value pairs

I have the following code, which works as expected - it properly filters out key/value pairs with value of null, false and returns an object with the other key/value pairs. However, it's rather verbose and I'm wondering if there's a way to simplify it.
const settings = {
distance: null,
length: 23,
weight: null,
isActive: false,
isRound: true
}
const data = {};
Object.entries(settings)
.filter(([, value]) => value !== null)
.filter(([, value]) => value !== false)
.forEach(([key, value]) => (data[key] = value));
console.log(data);
You can remove all falsy values and iterate just once:
const settings = {
distance: null,
length: 23,
weight: null,
isActive: false,
isRound: true
}
const data = {};
Object.entries(settings)
.forEach(([key, value]) => {
if(!!value) {
data[key]= value;
}
})
console.log(data);
What about using a reducer instead of your forEach?
const settings = {
distance: null,
length: 23,
weight: null,
isActive: false,
isRound: true
}
const data = Object.entries(settings)
.filter(([, value]) => value !== null && value !== false)
.reduce((acc, [key, value]) => {
(acc[key] = value);
return acc;
}, {});
console.log(data);
In vanilla javscript it would be more easier in a single loop
const settings = {
distance: null,
length: 23,
weight: null,
isActive: false,
isRound: true
}
const data = {}
for(var key in settings) {
if(settings[key] !== false && settings[key] !== null) {
data[key] = settings[key];
}
}
Not sure why to use multiple filter methods and reduce as it will cause additional iterations.
As an alternative, here's a bit of an arguably simpler, but not functional version that uses mutability to delete the offending keys:
const settings = {
distance: null,
length: 23,
weight: null,
isActive: false,
isRound: true
}
const copy = Object.assign({}, settings);
Object.entries(copy).forEach(([key, value]) => {
if (value === null || value === false) delete copy[key];
});
console.log(copy);
A bit shorter, if it's to your taste.
why don't you check these condition in one iteration
change this
.filter(([, value]) => value !== null)
.filter(([, value]) => value !== false)
to
.filter(([, value]) => !!value)
or make use of single loop which checks condition and append data in data object
eg:
Object.entries(settings)
.forEach(([key, value]) => {
if(!!value) {
data[key]= value;
}
})

how to convert api respons ekey value in camelcase

I am getting some api response , where I have to change all key value is camelcase ..
like below I am getting response . i have to convert all key is small .
like CBSServiceResponseDtls = cBSServiceResponseDtls same for all keys .
Dont want to change any of value And few of keys has _ .
Like "API_OUTPUT" I have to change in apiOutput.
Please help. Thanks
{
"CBSServiceResponseDtls":{
"Response":{
"ResultStatus":{
"ResultCode":0,
"ResultMessage":"SUCCESS"
},
"Data":{
"EVENT":{
"API_OUTPUT":{
"SUCCESS_FLAG":0,
"REQUEST_STATUS":0,
"ABILLITY_REF_NUM":32038,
"RESPONSE_ERROR_CODE":"",
"SUCCESS_MESG_LANG_1":"Request has been Processed Successfully",
"SUCCESS_MESG_LANG_2":"Request has been Processed Successfully",
"TRANSACTION_LOG_REFERNCE":2.0200507064507202e+27
},
"LOGIN_DETAILS":{
"LOGIN_DTLS":[
{
"LOGIN_NAME":"soban",
"LOCATION_CODE":"WAREHOUSE",
"LOCATION_CODE_NO":1,
"LOCATION_DESC":"WAREHOUSE",
"DEFAULT_FLAG":"Y"
},
{
"LOGIN_NAME":"soban",
"LOCATION_CODE":"BTP",
"LOCATION_CODE_NO":70,
"LOCATION_DESC":"BTP",
"DEFAULT_FLAG":"N"
}
]
}
}
}
}
}
}
Simple solution, You can use regex replace/transform function to change all keys.
More: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
CamelCase Sample:
const toCamelCase = (str = "") => {
const [first, ...rest] = str.split("_");
return (
first.toLowerCase() +
rest
.map((word) => {
word = word.toLowerCase();
return word.slice(0, 1).toUpperCase() + word.slice(1);
})
.join("")
);
};
const tranform = (m) =>
m.indexOf("_") === -1
? m.slice(0, 1).toLowerCase() + m.slice(1)
: toCamelCase(m);
const formatJson = (obj) => {
const jsonString = JSON.stringify(obj).replace(
/\"(\w+)\":/g,
(_, m) => `"${tranform(m)}":`
);
return JSON.parse(jsonString);
};
const data = {"CBSServiceResponseDtls":{"Response":{"ResultStatus":{"ResultCode":0,"ResultMessage":"SUCCESS"},"Data":{"EVENT":{"API_OUTPUT":{"SUCCESS_FLAG":0,"REQUEST_STATUS":0,"ABILLITY_REF_NUM":32038,"RESPONSE_ERROR_CODE":"","SUCCESS_MESG_LANG_1":"Request has been Processed Successfully","SUCCESS_MESG_LANG_2":"Request has been Processed Successfully","TRANSACTION_LOG_REFERNCE":2.0200507064507202e+27},"LOGIN_DETAILS":{"LOGIN_DTLS":[{"LOGIN_NAME":"soban","LOCATION_CODE":"WAREHOUSE","LOCATION_CODE_NO":1,"LOCATION_DESC":"WAREHOUSE","DEFAULT_FLAG":"Y"},{"LOGIN_NAME":"soban","LOCATION_CODE":"BTP","LOCATION_CODE_NO":70,"LOCATION_DESC":"BTP","DEFAULT_FLAG":"N"}]}}}}}}
const json = formatJson(data);
console.log("%j", json);
You can use this helper function to do it recursively:
function objToLowerCase(obj) {
if (Array.isArray(obj)) {
return obj.map((entry) => typeof entry !== "object" ? entry : objToLowerCase(entry));
}
const newObj = Object.entries(obj).reduce((obj, [key, value]) => {
const newKey = key.toLowerCase().split("_").join("");
const newValue = typeof value !== "object" ? value : objToLowerCase(value);
obj[newKey] = newValue;
return obj;
}, {});
return newObj;
}
Here is a sandbox.
To make it camelCase, you could use this function:
function toCamelCase(str) {
if (str === str.toUpperCase()) {
return str
.toLowerCase()
.split("_")
.map((s, i) =>
i === 0 ? s : s.slice(0, 1).toUpperCase() + s.slice(1, s.length)
)
.join("");
} else {
return str.slice(0, 1).toLowerCase() + str.slice(1);
}
}
and call it like this:
function objToLowerCase(obj) {
if (Array.isArray(obj)) {
return obj.map((entry) => typeof entry !== "object" ? entry : objToLowerCase(entry));
}
const newObj = Object.entries(obj).reduce((obj, [key, value]) => {
const newKey = toCamelCase(key)
const newValue = typeof value !== "object" ? value : objToLowerCase(value);
obj[newKey] = newValue;
return obj;
}, {});
return newObj;
}

Categories

Resources