Idiomatically accessing json objects with clojurescript - javascript

Anyone have any docs for idiomatic clojurescript for access a javascript object (returned as json, essentially a hash)?
I have a JSON object returned via an AJAX request:
{
list: [1,2,3,4,5],
blah: "vtha",
o: { answer: 42 }
}
How do I access these fields using clojurescript?
I can do:
(.-list data)
But how does this work when I have nested values and objects?
(.-answer (.-o data))
The above seems to be quite clumsy, especially given the nice js syntax of: data.o.answer.
What is the idiomatic way of accessing json objects with clojurescript?
Note:
I realised that I can actually refer to elements using JS syntax, which is quite handy actually. So the following will work correctly:
(str data.o.answer)

You probably want aget:
(aget foo "list")
aget isn't variadic yet, but hopefully will be soon
it's variadic now. (aget data "o" "answer") would work

Firstly, your proposed syntax for nested access does work:
ClojureScript:cljs.user> (def data
(JSON/parse "{\"list\": \"[1,2,3,4,5]\", \"blah\": \"vtha\", \"o\": {\"answer\": \"42\"}}"))
#<[object Object]>
ClojureScript:cljs.user> (.-answer (.-o data))
"42"
You can use the threading macros...
ClojureScript:cljs.user> (-> data (.-o) (.-answer))
"42"
Or .. notation
ClojureScript:cljs.user> (.. data -o -answer)
"42"

If you're dealing with any amount of data, I'd convert the JSON into clojure data structures and then use the usual idioms:
(let [my-json (js* "{
list: [1,2,3,4,5],
blah: \"vtha\",
o: { answer: 42 }
}")
converted (js->clj my-json)]
(get-in converted ["list" 3]) ;; => 4
(-> converted "o" "answer") ;;=> 42
)
(Note: don't use js* if you can help it; it's not idiomatic and might go away in future versions of ClojureScript.)

Clojurescript has a .. operator that is useful for chained javascript calls:
(.. data -o -answer) => data.o.answer => 42
(aget (.. data -list) 1) => data.list[1] => 2
You can use most list operators on arrays too, e.g.
(into [] (.. data -list)) ; vector [1 2 3 4]

Forget about aget, it's mainly designed for array (array get). Use
goog.object/get
goog.object/set
See more
https://clojurescript.org/news/2017-07-14-checked-array-access

Related

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

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

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();

How to get primitive values only from str.split() in an Ember js project

I simply want "just" the primitive values from a str.split() from within my Ember.js project, instead of getting the primitive split values, I get a waterfall of ember prototypes!
Example
for(var num in "1,2,3,4".split(",")){
console.log(num);
}
Result (without Ember):
0
1
2
3
Result (with Ember)
0
1
2
3
nextObject
firstObject
lastObject
contains
getEach
setEach
mapBy
mapProperty
reject
filterBy
filterProperty
rejectBy
rejectProperty
find
findBy
findProperty
everyBy
everyProperty
any
anyBy
someProperty
invoke
toArray
compact
without
uniq
[]
addEnumerableObserver
removeEnumerableObserver
hasEnumerableObservers
enumerableContentWillChange
enumerableContentDidChange
Here's my jsbin.. http://jsbin.com/rodul/1/edit?html,js,console
Thanks for any help!
It's because you iterate over every property within the returned array of split() and Ember adds a lot to the Array.prototype scope so that you're able to get firstObject or lastObject easily (for example) - see http://emberjs.com/api/classes/Ember.Array.html for more information.
To make a long story short: if you use forEach or a simple for loop, you will see the expected behaviour. See your updated jsbin here http://jsbin.com/dixihiqi/1/
In general, I would use a for in for objects rather than arrays... (but that's just me)

Traversing 'JSON Array of arrays' in python 'List of lists' format

For reasons I don't understand, the JSON format data I need to parse that comes out of a webservice isn't in nme value pairs. 'For simplicity and to reduce overhead' the returned JSON seems to be in a format that works with python eval but as far as I can see not with javascript (caveat, my javascript is very poor so I could be wrong - php etc, fine. js, not so much!)
So the data is returned as:
[[0, 'OK'],
[['ITEM10314', ['ITEM10397']],
['ITEM10315', ['cornflower']],
['ITEM10397', ['ITEM10315']],
['ITEM10514', ['ITEM10397']],
['ITEM003', []],
['ITEM004', []],
['servertest', ['ITEM004', 'ITEM003']],
['serverroot', []]]]
(in case you are interested, it is a reply from a MKLiveStatus for Nagios LQL host query)
The first array is the status, then subsequent arrays consist of the host monitored in nagios and that host's parents (in an inner array).
Nice, isn't it. But I need to get it into decent key/value pairs and there must be a better way than writing my own parser for this (not least because this is one data output but there are several more in similar formats).
I'm trying to keep this all in native js but if there is a jQuery easy way then I'm easily led to lazyness. No need to worry about old browsers, I don't care, this project ends up using d3.js which won't work on old browsers anyway.
Any suggestions? The depth in this case won't go below what it is here, so that at least is a known. However, I can't just flatten it, I need to know which parents a host has after this.
I have seen a few python-js links here but not arbitrary unknown sized lists in lists.
Something like this should do it
var data = [
[0, "OK"],
[
["ITEM10314", ["ITEM10397"]],
["ITEM10315", ["cornflower"]],
["ITEM10397", ["ITEM10315"]],
["ITEM10514", ["ITEM10397"]],
["ITEM003", []],
["ITEM004", []],
["servertest", ["ITEM004", "ITEM003"]],
["serverroot", []]
]
];
function parse(array) {
var object = {
ok: 1
};
if (!Array.isArray(array) && array[0][0] !== 0 && array[0][1] !== "OK") {
return object;
}
object.ok = 0;
array[1].forEach(function (element) {
object[element[0]] = element[1];
});
return object;
}
console.log(parse(data));
On jsfiddle

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