Recursively replace all occurances of large JSON keys having - to _ character - javascript

I have a json from Google pagespeed api, which needs to be exported to big query. Since BQ doesnt support the keys having - symbol, I must replace all the key names those have - char to _
Please note that I cannot perform a find and replace on entire string as the values need a - char. The json structure can be complex and I understand the only ways to do is by iterating all the keys of nested objects.
I found this https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
but could not iterate the nested json, as I cannot predetermine the json structure and all that must be dynamically decided Any pointers how to replace all chars would be great
The input JSON is here: https://drive.google.com/file/d/1lFYW26xsPGQp6WU9h6f2-E1bHW-hDIic/view?usp=sharing
const replaceKeys = obj => {
return Object.fromEntries(Object.entries(obj).map( ([key,value]) => {
return [
key.replace("-","_"),
Array.isArray(value)
? value.map(replaceKeys)
:typeof value == "object"
? replaceKeys(value)
: value
];
}))
}
const result = input.map(replaceKeys);
console.log(result);

You can do this transformation both, when you JSON.parse() the data from google, or when you JSON.stringify() the object to send it to big query.
imo. that's the most economical approach, to transform while parsing or serializing, instead of first parsing then transforming or first transforming then serializing.
// sources
const obj = {
"key-with-dashes": [{
"some-value": 42
}]
};
const json = JSON.stringify(obj);
// utilities
const includesDash = v => v.includes("-");
const replaceDashInKey = kv => {
kv[0] = kv[0].replace(/-/g, "_");
return kv;
}
const cleanupDashesInKeys = (k, v) => {
if (typeof v === "object" &&
v !== null &&
!Array.isArray(v) &&
Object.keys(v).some(includesDash)
) {
return Object.fromEntries(Object.entries(v).map(replaceDashInKey))
}
// this value has no keys with dashes
return v;
};
//cleanup keys
console.log("on parse", JSON.parse(json, cleanupDashesInKeys));
console.log("on stringify", JSON.stringify(obj, cleanupDashesInKeys));
.as-console-wrapper{top:0;max-height:100%!important}

Related

How to find Key value from an object based on the value passed

Thanks for your time.
I have the following object in JavaScript:
{
"key1":"value1,value2,value3",
"key2":"value4,value5,value6"
}
Now I want to parse for a specific value and if the value exists, I want to return the key associated with it. Suppose that I am passing 'value3', it should return key1; if passing value5, it should return me key2 and so on.
What is the best way to implement it using Javascript, keeping in mind the execution time. I have tried using sting manipulation functions like indexOf, substr; but not most effective I believe.
TIA.
Here is a slightly different approach that will generate a map where the key is actually the value of your original values object.
The generated map will be a sort of fast lookup. Just to make things clear this solution is efficient as long as you need to do a lot of lookups. For a single, unique lookup this solution is the less efficient, since building the hashmap requires much more time than just looking up for a single value.
However, once the map is ready, acquiring values through keys will be incredibly fast so, if you need to later acquire multiple values, this solution will be more suitable for the use case.
This can be accomplished using Object.entries and Object.values. Further explanations are available in the code below.
The code below (despite not required) will also take care of avoiding indexOf with limit cases like searching 'value9' over 'value9999' which, on a regular string, would actually work with indexOf.
const values = {
"key1":"value1,value2,value3",
"key2":"value4,value5,value6",
"key3":"value444,value129839,value125390", // <-- additional test case.
"key4": "value9" // <-- additional test case.
};
const map = Object.entries(values).reduce((acc, [key, value]) => {
// If the value is invalid, return the accumulator.
if (!value) return acc;
// Otherwise, split by comma and update the accumulator, then return it.
return value.split(',').forEach(value => acc[value] = key), acc;
}, {});
// Once the map is ready, you can easily check if a value is somehow linked to a key:
console.log(map["value444"]); // <-- key 3
console.log(map["value9"]); // <-- key 4
console.log(map["Hello!"]); // undefined
To me, the fastest and most concise way of doing that would be the combination of Array.prototype.find() and String.prototype.includes() thrown against source object entries:
const src={"key1":"value1,value2,value3","key2":"value4,value5,value6"};
const getAkey = (obj, val) => (Object.entries(obj).find(entry => entry[1].split(',').includes(val)) || ['no match'])[0];
console.log(getAkey(src, 'value1'));
console.log(getAkey(src, 'value19'));
p.s. while filter(), or reduce(), or forEach() will run through the entire array, find() stops right at the moment it finds the match, so, if performance matters, I'd stick to the latter
Lodash has a function for this called findKey which takes the object and a function to determine truthiness:
obj = { 'key1': 'value1, value2, value3', 'key2': 'value4,value5,value6' }
_.findKey(obj, val => val.includes('value3'))
# 'key1'
_.findKey(obj, val => val.includes('value5'))
# 'key2'
Based on your search, you can use indexOf after looping through your object.
Here's an old school method:
var obj = {
"key1":"value1,value2,value3",
"key2":"value4,value5,value6"
}
function search (str) {
for (var key in obj) {
var values = obj[key].split(',');
if (values.indexOf(str) !== -1) return key
}
return null;
}
console.log(search('value1'))
console.log(search('value6'))
Or you can use Object.keys() with filter() method and get the index 0 of the returned array.
var obj = {
"key1":"value1,value2,value3",
"key2":"value4,value5,value6"
}
function search (str) {
return Object.keys(obj).filter((key) => {
const values = obj[key].split(',');
if (values.indexOf(str) !== -1) {
return key
}
})[0]
}
console.log(search('value1'))
console.log(search('value6'))
You can try iterating over each value in your object and then splitting the value on each comma, then checking if the value is in the returned array like so:
const myObj = {"key1":"value1,value2,value3","key2":"value4,value5,value6"}
function findKeyByValue(obj, value) {
for (var key in myObj) {
const valuesArray = myObj[key].split(',')
if (valuesArray.includes(value)) {
return key
}
}
}
const key = findKeyByValue(myObj, 'value5') // returns 'key2'
console.log(key)
EDIT: Changed loop for efficiency, and extracted code to function
This should do it. Just uses Object.entries and filters to find the entries that contain the value you're looking for. (Can find more than one object that has the desired value too)
var obj = {
"key1": "value1,value2,value3",
"key2": "value4,value5,value6"
};
var find = 'value2';
var key = Object.entries(obj).filter(([k, v]) => v.split(',').includes(find))[0][0];
console.log(key);
Might want to check the return value of Object.entries(obj).filter((o) => o[1].split(',').includes(find)) before trying to access it, in case it doesn't return anything. Like so:
var obj = {
"key1": "value1,value2,value3",
"key2": "value4,value5,value6"
};
function findKey(objectToSearch, valueToFind) {
var res = Object.entries(objectToSearch).filter(([key, value]) => value.split(',').includes(valueToFind));
if(res.length > 0 && res[0].length > 0) {
return res[0][0];
}
return false;
}
console.log(findKey(obj, 'value5'));
includes can be used to check whether a value is present in an array. Object.keys can be used for iteration and checking for the match.
function findKey(json, searchQuery) {
for (var key of Object.keys(json)) if (json[key].split(',').includes(searchQuery)) return key;
}
const json = {
"key1": "value1,value2,value3",
"key2": "value4,value5,value6"
}
console.log(findKey(json, 'value5'))
Use Object.entries with Array.prototype.filter to get what the desired key.
const data = {
"key1": "value1,value2,value3",
"key2": "value4,value5,value6"
};
const searchStr = 'value3';
const foundProp = Object.entries(data).filter(x => x[1].indexOf(searchStr) !== -1);
let foundKey = '';
if (foundProp && foundProp.length) {
foundKey = foundProp[0][0];
}
console.log(foundKey);

Build nested JSON from string of nested keys [duplicate]

This question already has answers here:
Convert a JavaScript string in dot notation into an object reference
(34 answers)
Closed 5 years ago.
I have csv files that I am reading using nodeJS. I convert each file to text before reading.
Each line in the file have data delimited with =.
Each line looks something like
data.location.degree.text=sometexthere
The first portion before the "=" represents an index to a JSON object in my app. My aim is to parse this data and build a JSON representation of it so that the line above becomes
data:{
location:{
degree:{
text: 'sometexthere'
}
}
}
Using javascript/nodejs; How can I convert a string which is supposed to represent a sequence of nested JSON keys, into a JSON object like above?
You could split the path and make a check if the following element exist. If not assign an object to the new property.
Return then the value of the property.
At the end assign the value.
function setValue(object, path, value) {
path = path.replace(/[\[]/gm, '.').replace(/[\]]/gm, ''); //to accept [index]
var keys = path.split('.'),
last = keys.pop();
keys.reduce(function (o, k) { return o[k] = o[k] || {}; }, object)[last] = value;
}
var data = {};
setValue(data, 'location.degree.text', 'sometexthere');
console.log(data);
// result container
var res = {};
// input data
var inp = [
'data.location.degree.text=sometexthere',
'data.otherLocation.degree.otherText=foo',
'data.location.degree.otherText=bar',
'we.can.handle.values.that.are_undefined=',
'we.can.handle.values.that.contain_equals_signs=yes=we=can'
];
// recursive function
var pathToObject = function(resultReference, path)
{
// split path on dots
// e.g. data.location.degree.text=sometexthere
// -> ["data", "location", "degree", "text=sometexthere"]
var splitPathParts = path.split('.');
// if there is only one part, we're at the end of our path expression
// e.g. ["text=sometexthere"]
if (splitPathParts.length === 1){
// split "text=sometexthere" into ["text", "sometexthere"]
var keyAndValue = splitPathParts[0].split('=');
// set foo = bar on our result object reference
resultReference[keyAndValue.shift()] = keyAndValue.join('=');
return;
}
// the first element of the split array is our current key
// e.g. for ["data", "location", "degree", "text=sometexthere"],
// the currentKey would be "data";
var currentKey = splitPathParts.shift();
// if our object does not yet contain the current key, set it to an empty object
resultReference[currentKey] || (resultReference[currentKey] = {});
// recursively call ourselves, passing in
// the nested scope and the rest of the path.
// e.g. { data : {} } and 'location.degree.text=sometexthere'
pathToObject(resultReference[currentKey], splitPathParts.join('.'));
}
for (var i = 0; i < inp.length; i++)
{
pathToObject(res, inp[i]);
}
console.log(res);
ES6 syntax makes things slightly more terse:
'use strict';
const pathToObject = (resultReference, path) => {
let [currentKey, ...restOfPath] = path.split('.');
if (restOfPath.length === 0) {
let [k, ...v] = currentKey.split('=');
resultReference[k] = v.join('=');
return;
}
resultReference[currentKey] || (resultReference[currentKey] = {});
pathToObject(resultReference[currentKey], restOfPath.join('.'));
}
let res = {};
[
'data.location.degree.text=sometexthere',
'data.otherLocation.degree.otherText=foo',
'data.location.degree.otherText=bar',
'we.can.handle.values.that.are_undefined=',
'we.can.handle.values.that.contain_equals_signs=yes=we=can'
].forEach(x => pathToObject(res, x));
console.log(res);

Efficiently or more functionally create an object from an array

Is there a more functional way to create an object in JavaScript programatically without assigning each key individually?
For example, given this array (imagine it comes from an outside data source):
let arr = ['a=1', 'b=2', 'c=3'];
What is an easy way to convert this to an object like so?
let expectedResult = { a: '1', b: '2', c: '3'};
It's clunky to assign a new object and loop over the elements with a for or foreach. It would be nice if there were something akin to map that could yield such a final result.
Imagine you could do this:
arr
.map(item => new KeyValuePair(itemKey, itemValue)) // magically get itemKey/itemValue
.toObjectFromKeyValuePairs();
That'd be it right there. But of course there's no such function built in.
If you're looking for a more functional approach to the code, you could use a library such as Lodash which makes code more succinct.
You could use _.fromPairs to convert pairs of data in arrays to key-value pairs of an object.
const convert = arr => _(arr)
.map(s => _.split(s, '=', 2))
.fromPairs()
.value();
console.log(convert(['a=1', 'b=2', 'c=3']));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
You could use reduce, split and slice:
var arr = ['a=1', 'b=2', 'c=3'];
var out = arr.reduce(
function (output, input) {
if (typeof input === 'string') {
var key = input.split('=',1)[0];
output[key] = input.slice( key.length + 1 );
}
return output;
},
{}
);
I use the second argument of split to make it stop after the first = found. Then using slice on the input (treating it as an array of characters) allows the value to contain the = separator as in the case of a=b=c.
By using slice, the value will always be a string, even if it is an empty one. If you want to have null values you could change the line to:
output[key || null] = input.slice( key.length + 1 ) || null;
The type check for string is present since split throws error on null and undefined.
If you wanted to parse the current page's query string for example, you could do it using the above technique just like this:
function getQueryStringParams() {
var reEncodedSpace = /\+/g;
return location.search.length > 1 // returns false if length is too short
&& location.search.slice( 1 ).split( '&' ).reduce(
( output, input ) => {
if ( input.length ) {
if ( output === false ) output = {};
input = input.replace( reEncodedSpace, ' ' ); //transport decode
let key = input.split( '=', 1 )[ 0 ]; // Get first section as string
let value = decodeURIComponent( input.slice( key.length + 1) ); // rest is value
key = decodeURIComponent( key ); // transport decode
// The standard supports multiple values per key.
// Using 'hasOwnProperty' to detect if key is pressent in output,
// and using it from Object.prototype instead of the output object
// to prevent a key of 'hasOwnProperty' to break the code.
if ( Object.prototype.hasOwnProperty.call( output, key ) ) {
if ( Array.isArray( output[ key ] ) ) {
// Third or more values: add value to array
output[ key ].push( value );
} else {
// Second value of key: convert to array.
output[ key ] = [ output[ key ], value ];
}
} else {
// First value of key: set value as string.
output[ key ] = value;
}
}
return output;
},
false
);
}
The function returns false if the search is empty.
If you're willing to spare having one additional line for declaration, this could work for you. Although using a library like lodash or underscore, as mentioned in other answers would certainly help:
var arr = ['a=1', 'b=2', 'c=3'];
var expectedResult = {};
arr.map(function(value) {
var kv = value.split("=");
expectedResult[kv[0]] = kv[1];
return value
})
Try The Below Code.
let arr = ['a=1', 'b=2', 'c=3'];
let b=arr.toString();
b='{"'+(b.split('=').join('":"').split(',').join('","'))+'"}';
b=$.parseJSON(b);
console.log(b);
You will get the required output.

How to iterate through whole json in javascript

I can have a json object. This object is not the same everytime. It can change dynamically. It can have object of arrays, array of arrays, array of objects anything that follows http://www.json.org/ standard
I want to XML escape each of the leaf level json object.
var jsonObject = {};//is not standard will change dynamically
var xmlescape = require('xml-escape');
iterate through each of the json object
jsonObjectAtParticularLevel = xmlescape(jsonObjectAtParticulatLevel);
How do I iterate through the whole json object and change it?
I tried to use JSON.stringify and JSON.parse, but I don't think that would be efficient.
function replacer(key, value) {
if (typeof value === "string") {
return xmlescape(value);
}
return value;
}
var newJsonObject = JSON.parse(JSON.stringify(jsonObject, replacer));
I want to use something like a recursive loop which will iterate through the whole json. But I'm able to figure out how to parse the whole json.
JSON.parse with revival function should be enough
var object = {"id":"10", "class": [{"child-of-9": "AABC"}, {"9": "a"}]};
$(function() {
object = JSON.parse(JSON.stringify(object), function (k, v) {
if(typeof v == "string") {
return v + "changed";
}
return v;
})
console.log(JSON.stringify(object));
alert(JSON.stringify(object));
});
let obj = {
'name': '',
'age' : ''
}
Object.keys(obj).forEach(key => {
console.log(key, '--> List of keys in the object')
})

Remove empty brackets from a JSON object

I would like to remove the matching elements {}, and {} from a JSON string.
Input : "test": [{},{},{},{},{},{},{}],
Output : "test": [],
To do so, I tried :
var jsonConfig = JSON.stringify(jsonObj);
var jsonFinal = jsonConfig.replace(/[{},]/g, ''); // Remove global
var jsonFinal = jsonConfig.replace(/[{},]/, ''); // Remove brackets
console.log(jsonFinal);
and many more.
How can I remove only those set of elements from my JSON without impacting the other brackets and comma?
Do NOT attempt to modify JSON with string manipulation functions.
ALWAYS parse the JSON, transform the data, and re-stringify to JSON.
EDIT: this answer addresses your comment that the input data object will contain other potential keys that should be present in the output.
// a couple of procedures to help us transform the data
const isEmptyObject = x => Object.keys(x).length === 0;
const not = x => ! x;
const comp = f => g => x => f (g (x));
const remove = f => xs => xs.filter (comp (not) (f));
// your input json
let json = '{"test": [{},{},{"x": 1}], "test2": [{},{}], "a": 1, "b": 2}';
// parsed json
let data = JSON.parse(json);
// transform data
let output = JSON.stringify(Object.assign({}, data, {
// remove all empty objects from `test`
test: remove (isEmptyObject) (data.test),
// remove all empty objects from `test2`
test2: remove (isEmptyObject) (data.test2),
}));
// display output
console.log(output); // '{"test":[{"x":1}],"test2":[],"a":1,"b":2}'
I like the ES2015 answer of #naomik.
This is another alternative:
/**
* Remove empty objects or arrays
* #param {Object, Array} obj: the object to which remove empty objects or arrays
* #return {Any}
*/
const removeEmptyObject = (function() {
const isNotObject = v => v === null || typeof v !== "object";
const isEmpty = o => Object.keys(o).length === 0;
return function(obj) {
if (isNotObject(obj)) return obj;
if (obj instanceof Array) {
for (let i = 0; i < obj.length; i += 1) {
if (isNotObject(obj[i])) continue;
if (isEmpty(obj[i])) obj.splice(i--, 1);
else obj[i] = removeEmptyObject(obj[i]);
}
}
else {
for (let p in obj) {
if (isNotObject(obj[p])) continue;
if (!isEmpty(obj[p])) obj[p] = removeEmptyObject(obj[p]);
if (isEmpty(obj[p])) delete obj[p];
}
}
return obj;
}
}());
Now lets test the code:
var json = '{"test": [{},{},{"x": 1}], "test2": [{},{}], "test3":[[],[1,2,3],[]], "a": 1, "b": 2}';
var data = JSON.parse(json); //Object
var output = removeEmptyObject(data);
console.log(output);
console.log(removeEmptyObject(9));
console.log(removeEmptyObject(null));
console.log(removeEmptyObject({}));
You should work on the actual object not the string.
If you do, you can loop through the object and check if it has any properties. If it doesn't have any, you can remove it.
for(var prop in obj) {
if (obj.hasOwnProperty(prop)) {
//remove here
}
}
Setting aside the question of whether string manipulation is the best way to tidy up JSON data, your earlier attempts would remove all braces and commas, because [] in a regexp indicates "match any of the characters contained inside these brackets". If you were trying to treat those as literal characters, they'd need to be escaped: \[ or \]
You want something like .replace(/{},?/g,"") (which means "match all instances of the string {} or the string {}, -- the question mark makes the preceding character an optional match).
(This would, of course, remove all empty objects from the string, and has the potential to create invalid JSON given input like "foo: {}, bar: {}" -- so only use this if you're certain that your data will never include intentionally empty objects.)

Categories