I need to remove any slashes from all keys (and sub keys) in a JSON structure in order to convert it to XML, where a slash cannot occur in a tag name.
"langServices": {"en/ENGLISH_ONLY": "English"}
I imagine something along the lines of
var finalData = jsonstr.replace("en/", "en-");
, replacing all slashes with a dash. So it should also work for this: {"can/cancel" : "true"}, where I don't know what string will come before the slash.
var jsonIn = {
"some/other/key/with/slashes": "foo bar baz",
"langServices": {
"en/ENGLISH_ONLY": "English",
"can/cancel": "true"
}
};
function sanitizeKeysRecursively(objIn) {
Object.entries(objIn).forEach(function(kv) {
var sanitizedKey = kv[0].replace(/\//g, "-");
// call the function recursively on any values that are objects
if (typeof kv[1] === 'object') {
sanitizeKeysRecursively(kv[1]);
}
// set the sanitized key and remove the unsanitized one
if (sanitizedKey != kv[0]) {
objIn[kv[0].replace(/\//g, "-")] = kv[1];
delete objIn[kv[0]];
}
});
}
sanitizeKeysRecursively(jsonIn);
console.log(jsonIn);
Related
I have a node app from which I get some data. I have gone through other questions asked on SO..but Im unable to figure this out. I need to access the value of result . The code below shows the exact response I get from server. None of the methods described in other answers like JSON.parse() etc seem to work.
[{
query: {
"parameter1": "12",
"parameter2": "13",
"parameter3": 25
}
result: 6.58443
}]
EDIT : As mentioned in the comments below, unfortunately I cant fix this on the server side(Comes from an external source). I have to deal with this broken JSON on my end and extract the value of result.
EDIT 2 : Yes, there are multiple arrays like this. The content and comma part doesnt change. They are listed one after the other.
Despite the fact you can't receive that data though any library function that expects JSON like jQuery $.ajax() with dataType='json' option (you should use dataType="text" in this case to avoid premature error being triggered I mean).
...you obviously need to fix JSON syntax before parsing it (as I understood you already know).
If it is what you are asking for, your best bet is a regular expression search and replace.
If you know you won't get things such as '{bracket: "}"}' it is pretty simple:
Example:
var wrong = `
[{
"query": {
"parameter1": "12",
"parameter2": "13",
"parameter3": 25
}
"result": 6.58443
}]
`;
var good = wrong.replace(/}(?!\s*[,}\]])/g, '},');
var json = JSON.parse(good);
console.log(json);
This is the simplest example that fixes the input you provided.
Even though it does not fix the same problem after an end of array (']') and, most importantly, if it were fixed it (or simply the string ended with '}' instead of ']' it would added an extra ',' at the end messing up things again.
A more polite approach solving beferementioned issues is to replace the var good = ... row in previous code with this one:
var good = wrong.replace(/(}|])(?!\s*[,}\]])/g, '$1,')
.replace(/,\s*$/, '')
;
Now, you have a valid json object so accessing any property in it is pretty obvious. For example, json[0].result is what you asked for.
On the other hand, if you can have brackets inside literal strings, it will be much more difficult (even not impossible). But I figure out it would hardly be the case...
what you can do is to encapsulate your result with back-ticks to have a (valid) string literal, then get result with any method you want, for example a matching regex :
var arr = `[{
"query": {
"parameter1": "12",
"parameter2": "13",
"parameter3": 25
}
"result": 6.58443
}]`;
var match = arr.match(/("result": )(\d*.\d*)/);
console.log(match[2]);
The suggestions provided above..all point to a hacky way of solving this. The only way to solve this issue was with the help of good old Regex expressions. To my surprise..even though there are lots of libraries to handle JSON parsing etc, to solve edge cases(Common when dealing with small clients or unreliable data source), there is no library which can handle this scenario.
#Bitifet's answer is what solves this problem..with regex.
Purely for illustrative purposes, the below code "rewrites" JSON that is missing commas into JSON that has commas in the appropriate places. The advantage of using this over a replace or a regular expression is that this code guarantees that string literals are handled correctly:
const LEX_EXPR = (
'('
+ '"(?:\\\\(?:["\\\\/bfnrt]|u[a-fA-F0-9]{4})|[^"])*"|'
+ '-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?|'
+ '(?:true|false|null)'
+ ')|'
+ '([{\\[])|'
+ '([}\\]])|'
+ '([:,])|'
+ '(\\s+)|'
+ '(.)'
)
function lex(string) {
let tokens = []
let expr = new RegExp(LEX_EXPR, 'mguy')
let match = expr.exec(string)
while(match !== null) {
let [
value,
atom,
begin, end, sep,
whitespace,
junk
] = match
let type
if (atom != null) {
type = "atom"
} else if (begin != null) {
type = "begin"
} else if (end != null) {
type = "end"
} else if (sep != null) {
type = "sep"
} else if (whitespace != null) {
type = "whitespace"
} else {
// junk. ignore or raise exception
throw `Invalid character: ${junk}`
}
tokens.push({ type, value })
match = expr.exec(string)
}
return tokens
}
function shouldInsertComma(prev, cur) {
if (!prev || !cur) {
return false
}
if (prev.type == "begin" || prev.type == "sep") {
return false
}
return cur.type == "begin" || cur.type == "atom"
}
function rewrite(tokens) {
let out = []
let prevNonWhitespace = null
for (let i = 0; i < tokens.length; i++) {
let cur = tokens[i]
if (cur.type !== "whitespace") {
if (shouldInsertComma(prevNonWhitespace, cur)) {
out.push({ type: "sep", value: "," })
}
prevNonWhitespace = cur
}
out.push(cur)
}
return out
}
function joinTokens(tokens) {
return tokens.map(({ value }) => value).join('')
}
const invalid = `
{
"foo": {
"bat": "bing}"
"boo": "bug"
}
"result": "yes"
}
`
const rewritten = joinTokens(rewrite(lex(invalid)))
console.log(JSON.parse(rewritten)) // { foo: { bat: 'bing}', boo: 'bug' }, result: 'yes' }
I have a bit of a unique problem. I'm currently trying to hook up a JSON dataSource to a kendo grid. Because of this, there can be no # anywhere in the JSON because it messes with kendo. So I have to take data like this:
[
{"ID#": "1", "Prop1": "Val1#"},
{"ID#": "2", "Prop2": "Val2"},
]
and escape the # so that Kendo can understand it:
[
{"ID\#": "1", "Prop1": "Val1\#"},
{"ID\#": "2", "Prop2": "Val2"},
]
The biggest problem with this is that the JSON could resemble just about anything: there could be any number/name of keys/values.
My Attempts:
I tried to escape the # like so: var dataSource = JSON.parse(result.replace("#", "\\#")); However, JSON.parse throws an error when I try this:
SyntaxError: JSON.parse: bad escaped character at line 1 column 7 of the JSON data
Then I tried to replace the # with #. It turns out Kendo can only support HTML entities in rows, not in column headers, without getting a template error. So I'm able to replace property values like this:
var dataSource = JSON.parse(result, function(key, value) {
if(typeof value === "string") {
return value.replace("#", "#");
}
else {
return value;
}
});
This works for values! However, the keys could still contain # which will mess up my Kendo Grid. I tried to loop through each object and key/pair and replacing the key if it has a #, but for some reason when I call Object.keys the array that is returned is just 0s and 1s even though that's not what my data is named.
for (var object in dataSource) {
for (var property in object) {
if (object.hasOwnProperty(property)) {
var keys = Object.keys(object);
for(var key in keys) {
var oldName = key;
var newName = key.replace("#", "\#");
object[newName] = object[oldName];
delete object[oldName];
}
}
}
}
This portion above appears to have no effect. It doesn't even throw an error.
I also tried replacing the # with a string: hashliteral. Then, after the kendo grid was created, I tried using jQuery to replace all of the hashliteral with #.
var fixed = $("#grid").html().replace(/hashliteral/g, "#");
$("#grid").html(fixed);
This does work, but it breaks the kendo grid and all of the bindings. The grid functionality is a must, so I can't use this solution.
Just to Recap
I'm trying to remove/replace or escape all # in JSON data so that it functions correctly with my Kendo Grid.
I can't use JSON.stringify and replace the # with \\# because then JSON.parse fails.
I can't use # because Kendo headers cannot have special characters.
I can't use jQuery to modify the $("#grid").html() because it breaks the grid bindings.
The key can be anything. The value can also be anything.
The JSON is NOT nested or complex.
I cannot define the field/title on the KendoGrid columns because I have no idea what the field/title needs to be, the data is very dynamic.
I'm about ready to give up on this and just remove all # from the data. If anyone can think of some way to accomplish this: renaming the properties of an object so that all # are preceded by a \, it would be much appreciated. For the life of me I can't find a solution to this problem.
I think you were already on the right track, but it's easier just to create a new list of objects:
var newObjects = [];
for (var i = 0; i < dataSource.length; i++ ) {
var currentObject = dataSource[i];
var newObject = {};
for (var key in currentObject) {
if (currentObject.hasOwnProperty(key)) {
var newKeyName = key.replace("#", "__HASHLITERAL__");
var newValue = currentObject[key];
if(typeof newValue === "string") {
newValue = newValue.replace("#", "__HASHLITERAL__");
}
newObject[newKeyName] = newValue;
}
}
newObjects.push(newObject);
}
console.log(newObjects);
Hope it helps and i understood your problem correctly.
Edit: You can't have special chars like \ as a key, so you have to actually use something unique like ___something___ which you could replace then with whatever later.
I have a task to replace all keys in string pattern with their values. The input is something like that:
[
'{ "name": "John", "age": 13 }',
"My name is #{name} and I am #{age}-years-old"
]
And the output is this: 'My name is John and I am 13-years-old'.
So I come up with this:
function FillTemplate() {
if (arguments.length < 2 || arguments.length > 7) {
console.log('The input objects should be at least 1 and lesser than 7!');
}
for (let i = 0; i <= arguments.length - 2; i += 1) {
JSON.parse(arguments[i]);
for (let j = 0; j < Object.keys(arguments[i]).length; i += 1) {
let currentKey = Object.keys(arguments[i])[j];
console.log(currentKey);
}
}
}
I have a problem when i console.log(currentKey) i got only zeros but my idea is take the first object in the input then json.parse it next take all the keys in that object and with one loop take every single key separately and replace it in the pattern string with regex pattern. But this Object.keys return only zeros to me. Where is the problem?
Here you go:
<script>
var foo = {
"name" : "John",
"age" : 13
}
var string = "My name is #{name} and I am #{age}-years-old";
// Extract all templates (#{name}, #{age}, ...)
var matches = string.match(/#{[a-zA-Z]+?}/g);
if ( matches ) {
matches.forEach(function(templateStringToReplace) {
// Strip the special characters to dynamically get the indices of the object
templateString = templateStringToReplace.replace(/#|{|}/g, "");
string = string.replace(templateStringToReplace, foo[templateString])
});
}
alert(string);
Try the other way around, parse the template string first, then loop over the keys you need so you can reference them directly in the object.
Also, I have no idea what you're trying to do with the arguments object.
// Our source array containing a data string and a template string
var source = [
'{"name": "John", "age": 13 }',
'My name is #{name} and I am #{age}-years-old'
],
// helper function to grab all the parameters from a template string
parseTemplate = function parseTemplate( template ) {
var regex = /#\{(.+?)\}/g,
parameters = [],
nextParameter;
do {
// this regexp will grab the next series of characters surrounded by #{}
nextParameter = regex.exec(template);
if (nextParameter) parameters.push(nextParameter[1]);
}
// as long as there are parameters left, keep searching
while (nextParameter);
return parameters;
},
// population function, uses parseTemplate to get the parameters and then adds them to the template
populateTemplate = function populate( template, data ) {
var parametersToSaturate = parseTemplate(template);
// for each parameter found, repalce that parameter in the string with the value from the data
return parametersToSaturate.reduce(function( saturatedTemplate, parameter ) {
return saturatedTemplate.replace('#{' + parameter + '}', data[parameter] || ('#{' + parameter + '}'));
}, template);
},
result = populateTemplate( source[1], JSON.parse(source[0]) );
console.log(result);
As long as you keep the array returned from parseTemplate is the same order, you can reuse any parameter as many times in a string as you want. Any #{val} parameter not found in the data will just remain.
If you have multiple objects, you can just loop over them.
sources.forEach(function( source ) {
console.log(populateTemplate( source[1], JSON.parse(source[0]) ));
});
If your browser supports it, you can use actual JS template strings:
https://developer.mozilla.org/nl/docs/Web/JavaScript/Reference/Template_literals
I have a http server built in node.js and 'connect'. The web service currently parses JSON requests to an Object, does "stuff", and returns a synchronous response. The JSON is from an ecommerce cart and I end up with an Object that looks like (abridged version):
var myObj = {"request":{"industry":"social","transactionId":"gdfs23s","billing": {"addressLine1":"911 Fallen Street","addressLine2":"1 2"},"shipping":{"addressLine1":"1523 Willow Tree LAne","addressLine2":"unit 15"}}}
I want to clean up the data, performing tasks such as removing extra white space, normalizing postal abbreviation, ie street to st, etc.
I've written a series of regular expression that successfully achieve the cleansing/normalization.
However what I am unsure of is how to do this efficiently AND elegantly apply these cleanse processes to an Object in JS /Node.js. I will have scenarios where I want to cleanse request.billing.addressLine1, request.shipping.addressLine1 with the same regex pattern
I could of course do something like:
var addressCleaner= new RegExp("( str| street| strt)","g");
myObj.request.billing.addressLine1.replace(addressCleaner, "st");
myObj.request.shipping.addressLine1.replace(addressCleaner, "st");
But I dont feel this is very DRY and furthermore its not being done very "node"ishly.
Any suggestions or example approaches? I would like to avoid using a package like Mongoose etc. to do this as the type of normalizing i'm doing does not just consist of making sure a string is a string ,etc.
Thanks in advance.
So, I would suggest to have a hash with all normalizers, and seperately to have a list of properties,
which needs to be normalized. To have the idea here some code:
var Normalizers = {
// -> function
trim: function(str) { return str.trim(); },
// -> array [RegExp, StringToReplace]
street: [ /(str|street)/g, 'st']
//...
};
var properties = {
'request.billing.addressLine1': ['trim', 'street'],
// ..
};
obj_normalize(myObj, properties, Normalizers);
The code for obj_normalize/obj_getProprety/obj_setProperty I moved to the gist.
If your regex is applicable to every string found within the object you can simply recurse through the object and apply the regex to every string.
A general purpose object traversal function is very useful for something like this:
function object_traverse (name,obj,fn) {
obj = fn(name,obj);
if (obj instanceof Array) {
for (var n=0;n<obj.length;n++) {
obj[n] = object_traverse(n,obj[n],fn);
}
}
else if (typeof obj != "string" && typeof obj != "number") {
for (var n in obj) {
obj[n] = object_traverse(n,obj[n],fn);
}
}
return obj;
}
Now you can do this:
myObj = object_traverse('',myObj,function(name,obj){
if (typeof obj == "string" && name.match(/address/i)) {
obj = obj.replace(addressCleaner, "st");
}
return obj;
});
I'd have a model built from JSON files and serialize it as I see fit. This would avoid matching or searching for properties which couldn't possibly exist in the source. Some example:
function makeAddress(rawAddress) {
return { street: rawAddress["str"] ||
rawAddress["street"] ||
rawAddress["strt"],
. . . };
Being equipped with this function, say, then you have an array of "address" object, then converting them would be a matter of:
addresses.map(makeAddress);
I have a JSON string stored in a data attribute.
{
"active": true,
"icons": {
"activeHeader": "ui-icon-alert"
},
"animate": {
"duration": 1000,
"always": dMethod
}
}
And I have a function which named dMethod:
function dMethod() {
alert("DONE");
}
When I try to parse the string via JSON.parse I get an error said invalid character. I check and the dMethod is defined when the parse method was running and if I removed the ,"always":dMethod part then the parser worked correctly.
I can't use quotation marks around the dMethod because then the type will be string type instead of object function.
Any help would be appreciated.
Thanks,
Péter
EDIT:
Thanks you for all the answers. I make some clarification so maybe better you understand the problem. I make a really simple js library to make jqueryui unobstructive:
var juiObjects = ["accordion", "autocomplete", "button", "datepicker", "dialog", "menu", "progressbar", "slider", "spinner", "tabs", "tooltip"];
$(document).ready(function() {
for (var i = 0; i < juiObjects.length; i++) {
var attributeName = "data-" + juiObjects[i];
$("["+ attributeName + "]").each(function () {
var optionsValue = $(this).attr(attributeName);
var options = JSON.parse(optionsValue);
$(this)[juiObjects[i]](options);
});
}
});
I had to choice between JSON.parse and eval. But I think eval wouldn't be so good choice. And try to keep the "library" as simple as possible. But it looks like I have sparete the code along the widgets.
Functions are not valid data types in JSON (see http://en.wikipedia.org/wiki/JSON#Data_types.2C_syntax_and_example).
I think you have to deserialize it as a string, then post-process your object and set "always" to your method.
It can be done by quoting the dMethod, by executing the function on the window object using the [] syntax:
function dMethod() {
alert("DONE");
}
var json = '{"active":true,"icons":{"activeHeader":"ui-icon-alert"},"animate":{"duration":1000,"always":"dMethod"}}'; // quoted
var obj = JSON.parse(json);
window[obj.animate.always]();
JSON.parse expects a valid JSON string. So, if you want to use it you should quote the dMethod function. Isn't it possible to replace the string "dMethod" with the real function after the parsing.
You can't parse that string as JSON, because it's not valid JSON.
You can turn the string into an object by executing it using the eval function, but of course the usual warnings about executing anything dynamically applies. If you don't have full control over what's in the string, it might be possible to use for cross site scripting.
var obj = eval(json);
obj.always();
This is how to serialize object with its functions:
JSON.stringify(YOUR_OBJECT, function (key, value) {
if (typeof value === 'function') {
return value.toString();
}
return value;
});
and this is how to deserialize it back:
JSON.parse(YOUR_JSON_STRING, function (key, value) {
if (value
&& typeof value === "string"
&& value.substr(0,8) == "function") {
var startBody = value.indexOf('{') + 1;
var endBody = value.lastIndexOf('}');
var startArgs = value.indexOf('(') + 1;
var endArgs = value.indexOf(')');
return new Function(value.substring(startArgs, endArgs)
, value.substring(startBody, endBody));
}
return value;
});