How do I use JSON Stringify to replace all keys and values? - javascript

I'm working on a way that takes advantage of the replacer function argument in JSON.Stringify in JavaScript in order to change the word-case (toUpper /toLower case), the problem is my JSON is not straight key:value, some values are keys also and they have values themselves, so I need to go through all keys and values, check if the value is also a key and make sure I change the case (toUpper or toLower) for all keys and values.
I know that the replacer function in JSON.Stringify(object,ReplacerFunction) iterates through all keys and values and makes the modifications inside then return keys and values, but although I've been reading about this for a wWhile I can't apply it, and I am not sure if I should apply recursion inside the replacer function or how, any help is appreciated.
Code I had:
function _replacer(name,val){
if(typeof val != "object"){
return val.toString().toUpperCase()
}
if(typeof name != "object"){
return name.toString().toUpperCase()
}
console.log("key = "+name+" type: "+typeof name);
console.log("value ="+val+" type: "+typeof val);
}
Also:
function _replacer(name,val){
if(typeof val != "object" &&typeof val ==="string"){
return val=val.toUpperCase()
}
if(typeof name != "object" &&typeof name ==="string"){
name=name.toUpperCase()
}
return val;
}
Also , i eventually got to this stage :
var res = JSON.parse(JSON.stringify(j, function(key, value) {
return typeof value === "string" ? value.toUpperCase() : value
}));
but this code only capitalizes the very lower level values, not all the keys/values, the reason is because i can only return one value from the replacer function, which is in this case the value.

The replacer function in JSON.stringify, does not allow you to replace keys, as documented in MDN. It allows you to transform values, or to omit key/value pairs altogether (by returning undefined).
Your best bet is probably to transform the object before stringifying it. Underscore or Lodash would make this pretty easy, but you can do it natively without too much trouble like this:
const xform = obj => {
return Object.keys(obj).reduce((xformed, key) => {
let value = obj[key]
if (typeof value ==='string') value = value.toUpperCase()
else if (typeof value === 'object') value = xform(value)
xformed[key.toUpperCase()] = value
return xformed
}, {})
}
console.log(JSON.stringify(xform({a: 'b', c: 1, d: {e: 'f'}})))
// {"A":"B","C":1,"D":{"E":"F"}}
You could also use RegEx and replace after it is stringified if you are so inclined. The code is certainly shorter, but perhaps less readable:
const stringified = JSON.stringify({a: 'b', c: 1, d: {e: 'f'}})
console.log(stringified.replace(/".+?"/g, s => s.toUpperCase()))
// {"A":"B","C":1,"D":{"E":"F"}}

Related

JavaScript callback-function in PHP-Array [duplicate]

I have a JS object I would like to save in Local Storage for future use, and I cannot parse it to a string.
Code:
JSON.stringify({
a: 5,
b: function (param) {
return param;
}
})
Result:
"{"a":5}"
How do I save it for future use, if not with JSON?
(And creating my own Lexer-Parser to interupt string function I dont think is an option)
I'd recommend this approach:
Store arguments and the body in your json:
{"function":{"arguments":"a,b,c","body":"return a*b+c;"}}
Now parse json and instantiate the function:
var f = new Function(function.arguments, function.body);
I think it's save
Usually a question like this indicates an X/Y problem: You need to do X, you think Y will help you do that, so you try to do Y, can't, and ask how to do Y. It would frequently be more useful to ask how to do X instead.
But answering the question asked: You could use replacer and reviver functions to convert the function to a string (during stringify) and back into a function (during parse) to store a string version of the function, but there are all sorts of issues with doing that, not least that the scope in which the function is defined may well matter to the function. (It doesn't matter to the function you've shown in the question, but I assume that's not really representative.) And converting a string from local storage into code you may run means that you are trusting that the local storage content hasn't been corrupted in a malicious way. Granted it's not likely unless the page is already vulnerable to XSS attacks, but it's an issue to keep in mind.
Here's an example, but I don't recommend it unless other options have been exhausted, not least because it uses eval, which (like its close cousin new Function)) can be a vector for malicious code:
// The object
var obj = {
a: 5,
b: function (param) {
return param;
}
};
// Convert to JSON using a replacer function to output
// the string version of a function with /Function(
// in front and )/ at the end.
var json = JSON.stringify(obj, function(key, value) {
if (typeof value === "function") {
return "/Function(" + value.toString() + ")/";
}
return value;
});
// Convert to an object using a reviver function that
// recognizes the /Function(...)/ value and converts it
// into a function via -shudder- `eval`.
var obj2 = JSON.parse(json, function(key, value) {
if (typeof value === "string" &&
value.startsWith("/Function(") &&
value.endsWith(")/")) {
value = value.substring(10, value.length - 2);
return (0, eval)("(" + value + ")");
}
return value;
});
document.body.innerHTML = obj2.b(42);
The construct (0, eval)("(" + value + ")"); ensures that eval runs at global scope rather than within the scope of the reviver function. Normally eval has a magic ability to use the scope you call it in, but that only works when you call it directly. Indirect eval as shown (or just var e = eval; e("(" + value + ")");) doesn't have that magic ability, it runs at global scope.
You can't store functions in JSON.
The value in JSON may contain only string, number, object, array, true, false or null:
Check out it on JSON site.
One simple way of doing this is
var dstr = JSON.stringify( { a: 5
, b: x => x
}
, (k,v) => typeof v === "function" ? "" + v : v
);
I've taken to storing the function name, along with the parameter values, in an array, with the first item in the array being the function name prepended with a $, to separate them from normal arrays.
{
"object": {
"your-function": ["$functionName", "param-1", "param-2"],
"color": ["$getColor", "brand", "brand-2"],
"normal-array": ["normal", "array"]
...
}
}
In the above example I have Sass and JS functions to retrieve color values from a global map/object. Parsing the function in this manner naturally requires custom code, but in terms of "storing" functions in JSON, I like this way of doing it.
I have created JSON.parseIt() and JSON.stringifyIt() functions based on the first answer without using eval
JSON.stringifyIt = (obj)=>{
return(
JSON.stringify(obj, function(key, value) {
if (typeof value === "function") {
return "/Function(" + value.toString() + ")/";
}
if(typeof value === "string"){
return "/String(" + value.toString() + ")/"
}
return value;
})
)
}
JSON.parseIt=(json)=>{
return(
JSON.parse(json, function(key, value) {
if (typeof value === "string" &&
value.startsWith("/Function(") &&
value.endsWith(")/")) {
value = value.substring(10, value.length - 2);
var string = value.slice(value.indexOf("(") + 1, value.indexOf(")"));
if(/\S+/g.test(string)){
return (new Function(string,value.slice(value.indexOf("{") + 1, value.lastIndexOf("}"))))
}else{
return (new Function(value.slice(value.indexOf("{") + 1, value.lastIndexOf("}"))));
}
}
if (typeof value === "string" &&
value.startsWith("/String(") &&
value.endsWith(")/")){
value = value.substring(8, value.length - 2);
}
return value;
})
)
}
// DEMO
var obj = {
string:"a string",
number:10,
func:()=>{
console.log("this is a string from a parsed json function");
},
secFunc:(none,ntwo)=>{console.log(none + ntwo)} ,
confuse:"/Function(hello)/"
}
const stringifiedObj = JSON.stringifyIt(obj);
console.log("the stringified object is: ",stringifiedObj);
const parsedObj = JSON.parseIt(stringifiedObj);
// console.log("the parsed object is: ",parsedObj);
console.log(parsedObj.string);
console.log(parsedObj.number);
console.log(parsedObj.confuse);
parsedObj.func();
parsedObj.secFunc(5,6);
The problems I fixed were
Removed eval.
there was a problem in the stringifying and parsing that if I give a string like
"/Function(hello)/" will be a function when parsed
Made it to two functions
Added parameter insertation
For someone that still need include, for whatever reason, the function definition in JSON, this code can help (but can be slow depending object size):
function Object2JsonWithFunctions(o, space = null) {
var functionList = {}
var fnSeq = 0;
var snrepl = function(k,v){
if(typeof v === 'function'){
fnSeq++;
var funcName = `___fun${fnSeq}___`;
var funcText = ''+v;
functionList[funcName] = funcText
return funcName;
}
return v;
}
var RawJson = JSON.stringify(o, snrepl, space);
for(func in functionList){
var PropValue = `"${func}"`;
RawJson = RawJson.replace(PropValue, functionList[func])
}
return RawJson;}
The code will do the normal convert to JSON.
For functions, the original stringify will return as "prop":"function()..." (function as a string)... The code above will create a placeholder (e.g: "prop":"fn1") and create a function list... After, will replace every placeholder to original function body...

Converting array to valid JSON string without using JSON.stringify?

I'm trying to write a function that takes some object, for example a number, a string, a list, or a map (of key-value pairs); and returns a valid JSON representation of that input as a string.
I have already set up other json encoders for simple numbers and string inputs:
Input => Output
a number with value 123 => 123 (string)
a string with value abc => "abc" (string)
But I'm having issues converting an array such as [1,"2",3]
Input => Output
1,2,three array => [1,2,"three"] (string)
Here is my current code:
var my_json_encode = function(input) {
if(typeof(input) === "string"){
return '"'+input+'"'
}
if(typeof(input) === "number"){
return `${input}`
}
//This is causing my issue
if(Array.isArray(input)) {
console.log(input)
}
I could simply add and return JSON.stringify(input) to change it but I don't want to use that.
I know I can create some sort recursive solution as I have base cases set up for numbers and strings. I'm blocked on this and any help will be appreciated
Edit: So the solution as been provided below in the answers section! Thanks :)
For arrays take a recursive approach with the items.
const
json_encode = (input) => {
if (typeof input === "string") return `"${input}"`;
if (typeof input === "number") return `${input}`;
if (Array.isArray(input)) return `[${input.map(json_encode)}]`;
};
console.log(json_encode([1, 'foo', [2, 3]]));
console.log(JSON.parse(json_encode([1, 'foo', [2, 3]])));
You already have the function that converts scalar values to json values.
So, you can call this function for all array's member (e.g. using https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/map) and then join it (https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/join) and add '[' and ']' to the resulting string
PS: this approach will work also in situations when you have an array of arrays
Example of the implementation:
var my_json_encode = function(input) {
if(typeof(input) === "string"){
return '"'+input+'"'
}
if(typeof(input) === "number"){
return `${input}`
}
if(Array.isArray(input)) {
const formatedArrayMembers = input.map(value => my_json_encode(value)).join(',');
return `[${formatedArrayMembers}]`;
}
}

Strange behavior of JSON.parse() reviver function

I am using second parameter of JSON.parse() to modify the result, but I don't quite clear about the order of the function parameter and also how it work
I have read the document about the using of the reviver function (e.g https://www.ecma-international.org/ecma-262/6.0/#sec-json.parse and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse).
From what I understand, this function would work with object, and first parameter is key or property name, second function is value or property value. What I don't understand is the return value of the function.
This is what it is done in example
var obj1 = '{"a":1, "b":42}';
let text = JSON.parse(obj1, (key, value) => {
if (typeof value === 'number'){
return value * 2
}
else{
return value
}
}
)
console.log(text) // {"a": 2, "b": 84}
This work well. But when I try to modify the code since I know all value is number already
var obj1 = '{"a":1, "b":42}';
let text = JSON.parse(obj1, (key, value) =>{
return value * 2
})
console.log(text) // NaN
It is strange to me why when I delete the return value it doesn't work. I mean, with the function none of the value return undefined when I value*2 it. I then try another test
var obj1 = '{"a":1, "b":42}';
let text = JSON.parse(obj1, (key, value) => {
if (typeof value === 'number'){
console.log('This is in if',key, value)
return value * 2
}
else{
console.log('This is in else', key, value)
return value
}
}
)
console.log(text)
Another strange thing happen when the code in else statement run even when it suppose not to run because the condition is incorrect. And when it run it even print out the obj1 object, which I didn't include in the statement.
Because it will also iterate thorough the object which is {"a":1, "b":42}.It will start from most nested level and then will go the original value itself which is {"a":1, "b":42}.
According to MDN
If a reviver is specified, the value computed by parsing is transformed before being returned. Specifically, the computed value and all its properties (beginning with the most nested properties and proceeding to the original value itself) are individually run through the reviver
By the way you can shorten your function.
var obj1 = '{"a":1, "b":42}';
let text = JSON.parse(obj1, (_, value) => value * 2 || value)
console.log(text)

How to determine type of an expression inside string in Javascript?

I have one rest call that gives me information from query parameters and I need to determine whether they are a string or an int, array, boolean.
For example, if I have:
/.../something?id=1
I'll receive '1', but I know that's an integer.
Furthermore, I have:
/.../something?id=[1,2,3]
I'll receive '[1,2,3]' but I know it's an array. Finally, if I have:
/.../something?id=string
I'll receive 'string' and I should use it as a regular string.
Is regex the only way of doing that check for each type?
You can use JSON.parse with a catch block.
var options = ['12', '[1,2,3]', 'string', 'false', '{"x" : 2}', '/hey/', /hey/];
var parsed = options.map(x => {
try {
return JSON.parse(x)
} catch(e) {
return x;
}
});
var types = parsed.map(x => /\[object (.*)\]$/.exec(Object.prototype.toString.call(x))[1])
console.log(types);
Once you have a value
var value = "1"; //or whatever value after `id=`
you can apply this logic
var type = "";
var isNum = ( s ) => !isNaN(s);
var isObject = ( s ) => {
try { s = JSON.parse(s); return Array.isArray(s) ? "array" : "object" } catch( e ){ return false }
};
Now use them as
type = isNum( value ) ? "number" : ( isObject( s ) || "string" );
Note
If value is a function definition, it will still return a string
One way to solve this is to use trial-and-error methodologies:
If you have the value starting from [ and ending with ], you probably can assume that its an array. Use JSON.parse to be sure.
If first case doesn't match, use /^\d+$/ to test the string against integer values. Use parseInt() to be sure.
Lastly, if above both test cases fail, you can be sure that its a string, because strings can hold almost all type of values.
Savio,
One way you can handle it is to fetch the value and use switch/case statement with 'typeof' to see what they are.
Typeof explanation on MDN
// Numbers
typeof 37 === 'number';
typeof 3.14 === 'number';
// Strings
typeof 'bla' === 'string';
// Booleans
typeof true === 'boolean';
// use Array.isArray or Object.prototype.toString.call
// to differentiate regular objects from arrays
typeof [1, 2, 4] === 'object';

Javascript reserved word and object

I'm making a dictionary of words, so there are 1,000,000+ words.
The problem comes when I need to store the word constructor. I know this is a reserved word in javascript, but I need to add it to the dictionary.
var dictionary = {}
console.log(dictionary ['word_1'])
//undefined, this is good
console.log(dictionary ['word_2'])
//undefined, this is good
console.log(dictionary ['constructor'])
//[Function: Object]
// this cause initialization code to break
How can I fix this? I could muck with the it like key=key+"_" but that seems bad. Is there anything else I can do?
Instead of using a JS object, you could use the built-in Map type which uses strings/symbols as keys and does not conflict with any existing properties.
Replace
var dictionary = {} with var dictionary = new Map()
Override the constructor key as undefined
According to the MDN Object.prototype page, the only thing that isn't hidden by the __fieldname__ schema is the "constructor field". Thus, you could just initialize your objects via { 'constructor': undefined }.
However, you would have to make sure that in your for .. in statements would filter out all keys with undefined as their value, as it would pick up constructor as a "valid" key (even though it wouldn't before you specifically set it to undefined). I.E.
for(var key in obj) if(obj[key] !== undefined) { /* do things */ }
Check for types when getting/setting
Otherwise, you could just check the type when you 'fetch' or 'store' it. I.E.
function get(obj, key) {
if(typeof obj[key] !== 'function') // optionally, `&& typeof obj[key] !== 'object')`
return obj[key];
else
return undefined;
}
I think you should store all words and translation of them in an array. When you need to translate a word, you can use find method of Array.
For example:
var dict = [
{ word: "abc", translated: "xyz" },
...
];
Then:
var searching_word = "abc";
var translation = dict.find(function (item) {
return item.word == searching_word;
});
console.log(translation.translated);
// --> xyz
To achieve expected result , use below option of using index to get value of any key value
var dictionary = {};
var dictionary1 = {
constructor: "test"
};
//simple function to get key value using index
function getVal(obj, val) {
var keys = Object.keys(obj);
var index = keys.indexOf(val);//get index of key, in our case -contructor
return obj[keys[index]]; // return value using indec of that key
}
console.log(getVal(dictionary, "constructor"));//undefined as expected
console.log(getVal(dictionary1, "constructor"));//test
console.log(dictionary["word_1"]);
//undefined, this is good
console.log(dictionary["word_2"]);
//undefined, this is good
codepen - https://codepen.io/nagasai/pen/LOEGxM
For testing , I gave one object with key-constructor and other object without constructor.
Basically I am getting the index of key first and getting value using index

Categories

Resources