How to convert JS object to string representing a valid Python dict? - javascript

I need to write code that takes a JavaScript object and writes out a string that is a valid, nice-looking Python3 dict. If possible, I wish to do this with no external dependencies.
My current implementation is as follows:
const TRUE_PLACEHOLDER = "__replace_me_true__";
const FALSE_PLACEHOLDER = "__replace_me_false__";
const booleanToPlaceholderReplacer = (key, val) =>
val === true ? TRUE_PLACEHOLDER : val === false ? FALSE_PLACEHOLDER : val;
const objToPythonDictStr = (obj) =>
JSON.stringify(obj, booleanToPlaceholderReplacer, 4)
.replaceAll(`"${TRUE_PLACEHOLDER}"`, "True")
.replaceAll(`"${FALSE_PLACEHOLDER}"`, "False");
An example result of objToPythonDictStr demonstrating that it seems to work well:
>> console.log(objToPythonDictStr({foo: 1, bar: false, baz: { baz2: true }}))
{
"foo": 1,
"bar": False,
"baz": {
"baz2": True
}
}
(Note: one obvious issue with my code is that if either of the placeholder strings are used as actual strings in the data, they'll get replaced incorrectly. This is quite unlikely in my use case and I'm okay with that risk, but I'm open to a better implementation which would remove this flaw if it doesn't lead to a much more complex implementation.)
Assuming that the object passed to objToPythonDictStr is a JSON-serializable object, is my objToPythonDictStr reasonable and correct?
Specifically, are there any incompatibilities in the output of JSON serialization and Python dict syntax, other than boolean representation, which will cause issues when using the hacky methodology shown above?

Python already offers a json.loads(str) method for parsing valid JSON strings into Python objects, there is no reason to do it JS-side.
At least one thing your function is missing is the difference between null value in JSON strings and Python equivalent of None

Related

How to make JSON.parse() to treat all the Numbers as BigInt?

I have some numbers in json which overflow the Number type, so I want it to be bigint, but how?
{"foo":[[0],[64],[89],[97]],"bar":[[2323866757078990912,144636906343245838,441695983932742154,163402272522524744],[2477006750808014916,78818525534420994],[18577623609266200],[9008333127155712]]}
TLDR;
You may employ JSON.parse() reviver parameter
Detailed Solution
To control JSON.parse() behavior that way, you can make use of the second parameter of JSON.parse (reviver) - the function that pre-processes key-value pairs (and may potentially pass desired values to BigInt()).
Yet, the values recognized as numbers will still be coerced (the credit for pinpointing this issue goes to #YohanesGultom).
To get around this, you may enquote your big numbers (to turn them into strings) in your source JSON string, so that their values are preserved upon converting to bigint.
As long as you wish to convert to bigint only certain numbers, you would need to pick up appropriate criteria (e.g. to check whether the value exceeds Number.MAX_SAFE_INTEGER with Number.isSafeInteger(), as #PeterSeliger has suggested).
Thus, your problem may be solved with something, like this:
// source JSON string
const input = `{"foo":[[0],[64],[89],[97]],"bar":[[2323866757078990912,144636906343245838,441695983932742154,163402272522524744],[2477006750808014916,78818525534420994],[18577623609266200],[9008333127155712]]}`
// function that implements desired criteria
// to separate *big numbers* from *small* ones
//
// (works for input parameter num of type number/string)
const isBigNumber = num => !Number.isSafeInteger(+num)
// function that enquotes *big numbers* matching
// desired criteria into double quotes inside
// JSON string
//
// (function checking for *big numbers* may be
// passed as a second parameter for flexibility)
const enquoteBigNumber = (jsonString, bigNumChecker) =>
jsonString
.replaceAll(
/([:\s\[,]*)(\d+)([\s,\]]*)/g,
(matchingSubstr, prefix, bigNum, suffix) =>
bigNumChecker(bigNum)
? `${prefix}"${bigNum}"${suffix}`
: matchingSubstr
)
// parser that turns matching *big numbers* in
// source JSON string to bigint
const parseWithBigInt = (jsonString, bigNumChecker) =>
JSON.parse(
enquoteBigNumber(jsonString, bigNumChecker),
(key, value) =>
!isNaN(value) && bigNumChecker(value)
? BigInt(value)
: value
)
// resulting output
const output = parseWithBigInt(input, isBigNumber)
console.log("output.foo[1][0]: \n", output.foo[1][0], `(type: ${typeof output.foo[1][0]})`)
console.log("output.bar[0][0]: \n", output.bar[0][0].toString(), `(type: ${typeof output.bar[0][0]})`)
.as-console-wrapper{min-height: 100% !important;}
Note: you may find RegExp pattern to match strings of digits among JSON values not quite robust, so feel free to come up with yours (as mine was the quickest I managed to pick off the top of my head for demo purposes)
Note: you may still opt in for some library, as it was suggested by #YohanesGultom, yet adding 10k to your client bundle or 37k to your server-side dependencies (possibly, to docker image size) for that sole purpose may not be quite reasonable.

How to serialize 64-bit integer in JavaScript?

Datadog Tracing API requires 64-bit integers serialized as JSON numbers.
{
"span_id": 16956440953342013954,
"trace_id": 13756071592735822010
}
How can I create JSON with 64-bit integer numbers using JavaScript?
This is actually a lot harder than it seems.
Representing big integers in JavaScript can be done using the BigInt data type (by suffixing the number with n), which is fairly widely supported at this point.
This would make your object look like this:
const o = {
span_id: 16956440953342013954n,
trace_id: 13756071592735822010n
};
The problem presents itself in the JSON serialization, as there is currently no support for the serialization of BigInt objects. And when it comes to JSON serialization, your options for customization are very limited:
The replacer function that can be used with JSON.stringify() will let you customize the serialization behavior for BigInt objects, but will not allow you to serialize them as a raw (unquoted) string.
For the same reason, implementing the toJSON() method on the BigInt prototype will also not work.
Due to the fact that JSON.stringify() does not seem to recursively call itself internally, solutions that involve wrapping it in a proxy also will not work.
So the only option that I can find is to (at least partially) implement your own JSON serialization mechanism.
This is a very poor man's implementation that calls toString() for object properties that are of type BigInt, and delegates to JSON.stringify() otherwise:
const o = {
"span_id": 16956440953342013954n,
"trace_id": 13756071592735822010n
};
const stringify = (o) => '{'
+ Object.entries(o).reduce((a, [k, v]) => ([
...a,
`"${k}": ${typeof v === 'bigint' ? v.toString() : JSON.stringify(v)}`
])).join(', ')
+ '}';
console.log(stringify(o));
Note that the above will not work correctly in a number of cases, most prominently nested objects and arrays. If I were to do this for real-world usage, I would probably base myself on Douglas Crockford's JSON implementation. It should be sufficient to add an additional case around this line:
case "bigint":
return value.toString();

Converting large number to string in Javascript/Node

I have seen other related questions, but they did not solve my problem, or may be I somehow missed the exactly same resolved queries.
Here is the problem. The service that I call up returns a JSON response with some keys having large numbers as values, and I want to pass them on to my view and display. The issue is that they are getting rounded off, which I don't want. Actually its coming inside a buffer from which I am doing now:
JSON.parse(res.body.toString()) // res.body is a Buffer
and sending to view. How can I retain the whole number in the form of a string and send this to view so exactly the same is made available to UI.
I thought may be a replacer will help, but it does not works too.
const replacer = (key, value) => {
if (typeof value === 'number') {
return JSON.stringify(value);
}
return value;
};
//78787878977987787897897897123456786747398
const obj = {
name: 'John',
income: 78787878977987787897897897123456786747398,
car: null
};
var buf = Buffer.from(JSON.stringify(obj));
console.log(buf.toString());
// console.log(JSON.stringify(buf.toString()))
// console.log('func res: ', replacer('key', 78787878977987787897897897123456786747398))
// console.log(obj.income.toString())
console.log(JSON.stringify(obj, replacer));
You can recommend some external trusted library, or better, suggest me the solution through direct code only.
Edit:
The outcome in short is: Convert the response to String before returning from the server. Once it gets into JS (Buffer in my case), the conversion already occurred meaning that from the application side, nothing can be done to retrieve it.
Please let me know if there's a real solution to this without modifying server response.
Unfortunately, the number is higher than max_safe_integer, so if it ever gets parsed as a number, even if it's converted back to a string later (such as with the reviver function, the second parameter to JSON.parse), it won't be reliable. But luckily, since you have a JSON string, you can replace numeric values with string values before JSON.parseing it. For example:
const resBody = '{"foo":"bar", "objs":[{"name":"John", "income": 78787878977987787897897897123456786747398}]}';
const resBodyReplaced = resBody.replace(/: *(\d+)/g, ':"$1"');
console.log(JSON.parse(resBodyReplaced).objs[0].income);

Best way to convert string to array of object in javascript?

I want to convert below string to an array in javascript.
{a:12, b:c, foo:bar}
How do I convert this string into array of objects? Any cool idea?
I think that the best way of doing this, as Douglas Crockford (one of the biggests gurus of JavaScript) suggests in here is using the JSON native parser, as it is not only faster than the eval(), it's also more secure.
Native JSON parser is already available in:
Firefox 3.5+
IE 8+
Opera 10.5+
Safari Safari 4.0.3+
Chrome (don't know which version)
And Crockford has made a safe fallback in javascript, called json2.js, which is an adaption of the eval() approach, with some security bits added and with the native JSON parsers API. You just need to include that file, remove its first line, and use the native JSON parser, and if it's not present json2 would do the work.
Here is an example:
var myJSONString = '{ "a": 1, "b": 2 }',
myObject = JSON.parse(myJSONString);
Once parsed you'll get an object with attributes a and b, and as you may know, you can treat an object as a hash table or associative array in JavaScript, so you would be able to access the values like this:
myObject['a'];
If you just want a simple array and not an associative one you could do something like:
var myArray = [];
for(var i in myObject) {
myArray.push(myObject[i]);
}
Lastly, although not necessary in plain JavaScript, the JSON spec requires double quoting the key of the members. So the navite parser won't work without it. If I were you I would add it, but if it is not possible use the var myObject = eval( "(" + myString + ")" ); approach.
Since your string is malformed JSON, a JSON parser can't parse it properly and even eval() will throw an error. It's also not an Array but a HashMap or simply an Object literal (malformed). If the Object literal will only contain number and string values (and no child objects/arrays) you can use the following code.
function malformedJSON2Array (tar) {
var arr = [];
tar = tar.replace(/^\{|\}$/g,'').split(',');
for(var i=0,cur,pair;cur=tar[i];i++){
arr[i] = {};
pair = cur.split(':');
arr[i][pair[0]] = /^\d*$/.test(pair[1]) ? +pair[1] : pair[1];
}
return arr;
}
malformedJSON2Array("{a:12, b:c, foo:bar}");
// result -> [{a:12},{b:'c'},{foo:'bar'}]
That code will turn your string into an Array of Objects (plural).
If however you actually wanted a HashMap (Associative Array) and NOT an array, use the following code:
function malformedJSON2Object(tar) {
var obj = {};
tar = tar.replace(/^\{|\}$/g,'').split(',');
for(var i=0,cur,pair;cur=tar[i];i++){
pair = cur.split(':');
obj[pair[0]] = /^\d*$/.test(pair[1]) ? +pair[1] : pair[1];
}
return obj;
}
malformedJSON2Object("{a:12, b:c, foo:bar}");
// result -> {a:12,b:'c',foo:'bar'}
The above code will become a lot more complex when you start nesting objects and arrays. Basically you'd have to rewrite JSON.js and JSON2.js to support malformed JSON.
Also consider the following option, which is still bad I admit, but marginally better then sticking JSON inside an HTML tag's attribute.
<div id="DATA001">bla</div>
<!-- namespacing your data is even better! -->
<script>var DATA001 = {a:12,b:"c",foo:"bar"};</script>
I am assuming you omit quote marks in the string because you had put it inside an HTML tag's attribute and didn't want to escape quotes.
The simplest, but unsafe way to do it is:
eval('(' + myJSONtext + ')')
But since this will interpret any javascript code, it has security holes. To protect against this use a json parser. If you're using a framework (jquery, mootools, etc.) there's a framework-specific call. Most of them are based on Douglas Crawford's parser available at http://www.json.org/js.html.
You can use "for in"
var myObject = {a:'12', b:'c', foo:'bar'};
var myArray = [];
for(key in myObject) {
var value = myObject[key];
myArray[key] = value;
}
myArray['a']; // returns 12
Notes: considering that myObject only have one level of key-value pairs.
JSON.parse will do the trick. Once parsed, you can push them into the array.
var object = JSON.parse(param);
var array = [];
for(var i in object) {
array.push(object[i]);
}
If you're using jQuery, there's the $.parseJSON() function. It throws an exception if the string is malformed, and "Additionally if you pass in nothing, an empty string, null, or undefined, 'null' will be returned from parseJSON. Where the browser provides a native implementation of JSON.parse, jQuery uses it to parse the string"
Use safe evaluation. Unlike JSON.parse, this doesn't require the keys or values to be quoted. Quote values only if they contain embedded commas.
const myStr = "{a:1, b:2, c:3}";
const myObj = string_exp(myStr);
console.log("dot: " + myObj.c);
function string_exp(sCmd) {
return Function(`'use strict'; return (${sCmd})`)();
}
https://dev.to/spukas/everything-wrong-with-javascript-eval-35on#:~:text=the%20variable%20exists.-,Alternatives,-The%20most%20simple

Javascript: using tuples as dictionary keys

I have a situation where I want to create a mapping from a tuple to an integer. In python, I would simply use a tuple (a,b) as the key to a dictionary,
Does Javascript have tuples? I found that (a,b) in javascript as an expression just returns b (the last item). Apparently this is inherited from C.
So, as a workaround, I thought I can use arrays instead,
my_map[[a,b]] = c
I tried it at the Firebug console and it seemed to work. Is that a good way to do it?
Another alternative I thought of is to create a string out of the tuples
my_map[""+a+":"+b] = c
So the question is: is there any problem with any of these methods? Is there a better way?
EDIT:
Small clarification: in my case, a,b,c are all integers
EcmaScript doesn't distinguish between indexing a property by name or by [], eg.
a.name
is literally equivalent to
a["name"]
The only difference is that numbers, etc are not valid syntax in a named property access
a.1
a.true
and so on are all invalid syntax.
Alas the reason all of these indexing mechanisms are the same is because in EcmaScript all property names are strings. eg.
a[1]
is effectively interpreted as
a[String(1)]
Which means in your example you do:
my_map[[a,b]] = c
Which becomes
my_map[String([a,b])] = c
Which is essentially the same as what your second example is doing (depending on implementation it may be faster however).
If you want true value-associative lookups you will need to implement it yourself on top of the js language, and you'll lose the nice [] style access :-(
You could use my jshashtable and then use any object as a key, though assuming your tuples are arrays of integers I think your best bet is one you've mentioned yourself: use the join() method of Array to create property names of a regular object. You could wrap this very simply:
function TupleDictionary() {
this.dict = {};
}
TupleDictionary.prototype = {
tupleToString: function(tuple) {
return tuple.join(",");
},
put: function(tuple, val) {
this.dict[ this.tupleToString(tuple) ] = val;
},
get: function(tuple) {
return this.dict[ this.tupleToString(tuple) ];
}
};
var dict = new TupleDictionary();
dict.put( [1,2], "banana" );
alert( dict.get( [1,2] ) );
All object keys in Javascript are strings. Using my_map[[a,b]] = c will produce a key in my_map which is the result of [a,b].toString(): a.toString() + ',' + b.toString(). This may actually be desirable (and is similar to your use of a + ':' + b), but you may run into conflicts if your keys contain the separator (either the comma if you use the array as the key, or the colon if you write the string as you have in your example).
Edit: An alternate approach would be to keep a separate array for key references. Eg:
var keys = [
[a,b],
[c,d]
];
var my_map = {
'keys[0]': /* Whatever [a,b] ought to be the key for */,
'keys[1]': /* Whatever [c,d] ought to be the key for */
};
the most simple and "natural" way to achieve something similar is by using multidimensional arrays, like this:
var my_map = [["blah","blah","bla"],
["foo", "bla", 8],
[324, 2345, 235],
[true, false, "whatever..."]];

Categories

Resources