Use JSONata to alpha sort keys in a JSON object - jsonata

I'll start off by saying I'm aware that, acording to json.org, "An object is an unordered set of name/value pairs." Nonetheless, in the real world, sometimes it would be nice to view keys in alphabetical order.
Unsorted:
{"B":2,"A":1,"C":3}
Sorted:
{"A":1,"B":2,"C":3}
Is there a way to do this in JSONata? (I understand I can pre- or post-process the data outside JSONata, but am curious whether there's a way to do this via JSONata.)
Thank you.

I have just written the following function:
$sortObjectAlphabetically := function($obj){
(
$keys := [$keys($obj)];
$keys := $keys^(<$);
$merge([$map($keys,function($e){
(
{
$e : $lookup($obj,$e)
}
)
})]);
)
};
I've done an example at this link: https://try.jsonata.org/RbJqQxO0i

Related

Is it possible to work with more than one array in vuetify v-autocomplete?

Normally one would just make a simple join to merge both arrays in one array, the problem is that i have arrays with different object structures, and depending on the type of object, i need to pass a different value.
Example:
array 1: fruits.type.name
array 2: animals.family.name
Is there any possibility other than having to craft a custom component from scratch using something like v-text-input, for example?
You mean something like this? Check this codesanbox I made:
https://codesandbox.io/s/stack-71429578-switch-autocomplete-array-757lve?file=/src/components/Example.vue
computed: {
autoArray() {
return this.typeAnimal ? this.animals : this.fruits
},
autoTypeId() {
return this.typeAnimal ? 'family.id' : 'type.id'
},
autoText() {
return this.typeAnimal ? 'family.name' : 'type.name'
}
}
With help of a couple computed props you could be able to switch array, item-text and item-value depending of the array you're working with.
As far as I know, there's no easy way to supply two different arrays to v-autocomplete and retain the search functionality.
You could probably join the arrays and write a custom filter property. Then use selection and item slots to change the output of the select based on the structure.
But if your data arrays aren't too complicated, I would avoid the above. Instead, I would loop through both arrays, and build a new combined one with a coherent structure.

In XPath 3.1's array:filter, can I specify a filtering function that takes more than one argument (i.e. a value to test the item *against*)?

I want to filter an array of maps based on a value for one of the keys. The problems seems to be that the second parameter of array:filter() is a function that accepts only a single item parameter: array:filter($array as array(*), $function as function(item()*) as xs:boolean) as array(*) according to the XPath and XQuery Operators 3.1 specs.
But when I do array:filter($routingTable, function ($i) {$i?input ne $wid}), with $wid being defined somewhere before this call, this variable is not being passed into the function and the comparison misses the crucial entries. (Checking the value of $wid with debugging output right before the call to array:filter confirms it has the correct value. Checking $i?input inside the anonymous functions confirms this value, too. But checking $wid inside the anonymous function makes it appear it is empty.)
So I thought maybe I need to pass the value to compare against to the filtering function as a second parameter, but when I do array:filter($routingTable, function ($i, $wid) {etc... , I get a java.lang.ArrayIndexOutOfBoundsException error. I assume this is due to the excess argument of the function. How should I go about this?
For what it's worth, my XQuery processor is eXist-db (6.1.0) and here is more complete code (the call that's at issue (I assume) is the second line of the last function):
(: for a sequence of nodes, I build maps and put them into an array. that is my routing table that is then posted :)
declare function my:createRoutes($wid as xs:string) {
let $index := doc($config:index-root || "/" || $wid || ".xml")/my:index
let $routingTable := array{fn:for-each($index//my:node, function($k) {my:buildRoutingInfoNode($wid, $k)} )}
return my:postRoutingTable($routingTable)
};
(: helper function to create a map from a node :)
declare function my:buildRoutingInfoNode($wid as xs:string, $item as element(my:node)) {
map { "input" : concat($wid, ":", $item/#citeID/string()), "outputs" : array { ( $item/#crumb/string(), 'yes' ) } }
};
(: here the routing table is posted. However, if the entries are already present in the "live" table, I need to clean them from there first :)
declare function my:postRoutingTable($routes as array(*)) as xs:integer {
if (array:size($routes) = 0) then
0
else
let $testmap := $routes?1 (: okay, that's a bit cheap: I just check the first of the new entries. :)
let $src := $testmap?input
let $dest := $testmap?outputs
return if (not(my:isInRoutingTable($src, $dest))) then
... post via http request ...
else (: At least one key is already present, need to clean routing table for $wid first :)
let $wid := substring-before($src, ":")
let $cleanStatus := my:cleanRoutingTable($wid)
return if ($cleanStatus ge 0) then
my:postRoutingTable($routes) (: retry ... :)
else
-1 (: cleaning failed ... :)
};
(: remove all entries about the $wid (so that I can add them again) :)
declare function my:cleanRoutingTable($wid as xs:string) as xs:integer {
let $routingTable := my:getRoutingTable() (: get "live" table :)
let $cleanedRT := array:filter($routingTable, function ($i) {
substring($i?input, 1, 5) ne $wid
}) (: remove all entries concerning the to-be-posted $wid :)
let $deleteStatus := my:deleteRoutingTable() (: drop the complete live table :)
return if (array:size($cleanedRT) > 0) then (: if after removing entries, anything is left of the original "live" table, :)
my:postRoutingTable($cleanedRT) (: then re-post this "cleaned" table :)
else -1
};
On the face of it this looks like an eXist-db bug. The variable $wid is part of the closure of the anonymous function and its value should be accessible; your code looks fine -- though without a complete repro (source document and expected results) I haven't tested it elsewhere.
Passing a second variable to a filter function
To dynamically filter a list of items, one often needs to pass a second, dynamic parameter.
The usual way of a lambda or anonymous function that reads a scoped variable would be:
let $upper-bound := 4 (: this might be read from user input :)
return filter(1 to 9, function ($item) {
$item < $upper-bound
})
With a little meta programming and leveraging the argument placeholder ? this can also be rewritten as
declare function local:filter ($item, $upper-bound) {
$item < $upper-bound
};
let $upper-bound := 4
return filter(1 to 9, local:filter(?, $upper-bound))
local:filter(?, $upper-bound) will return a function with an arity of 1 which is exactly what fn:filter and array:filter expect.
Main benefit is that it allows to reuse filter functions (here local:filter).
This function can now also be defined in an imported module.

Go equivalent of Python's Dictionary

I am looking for a way to store multiple values for each key (just like we can in python using dictionary) using Go.
Is there a way this can be achieved in Go?
Based on your response in comments I would suggest something like the following using a struct (though if you are only interested in a single value like name for each item in your slice then you could just use a map[int][]string{}
type Thing struct {
name string
age int
}
myMap := map[int][]Thing{}
If you want to add things then you just do...
myMap[100] = append(myMap[100], Thing{"sberry": 37})
Or if you want to create it in place:
myMap := map[int][]Thing{
100: []Thing{Thing{"sberry", 37}},
2: []Thing{Thing{"johndoe", 22}},
}
EDIT: Adding a "nameIn" function based on comments as demo:
func nameIn(things []Thing, name string) bool {
for _, thing := range things {
if thing.name == name {
return true
}
}
return false
}
if nameIn(myMap[100], "John") {
...
If the slices are really big and speed is concern then you could keep a reverse lookup map like map[string]int where an entry would be John: 100, but you will need to most likely use some user defined functions to do your map modifications so it can change the reverse lookup as well. This also limits you by requiring unique names.
Most likely the nameIn would work just fine.
In go, the key/value collection is called a map. You create it with myMap := map[keyType]valType{}
Usually something like mapA := map[string]int{}. If you want to store multiple values per key, perhaps something like:
mapB := map[string][]string{} where each element is itself a slice of strings. You can then add members with something like:
mapB["foo"] = append(mapB["foo"], "fizzbuzz")
For a great read see Go maps in action

How to iterate over a map in XQuery?

Let's say I have map defined in XQuery like this:
declare namespace map="http://www.w3.org/2005/xpath-functions/map";
let $x := map{'a':1, 'b':2}
How do I iterate through $x without knowing the keys?
For example:
for $key, $value in $x
(: Some processing and output :)
From the BaseX wiki on XQuery 3.1 features:
The fact that a map is a function item allows it to be passed as an argument to higher-order functions that expect a function item as one of their arguments. As an example, the following query uses the higher-order function fn:map($f, $seq) to extract all bound values from a map:
let $map := map { 'foo': 42, 'bar': 'baz', 123: 456 }
return fn:for-each(map:keys($map), $map)
So, let's say that you wanted to call do-some-processing() with each key and value:
declare namespace map="http://www.w3.org/2005/xpath-functions/map";
declare variable $map := map{'a':1, 'b':2};
declare function local:do-some-processing($key, $value) {
(: deciding what to put here is your problem, not mine :)
<key id="{$key}">{$value}</key>
};
fn:for-each(
map:keys($map),
function($k) { local:do-some-processing($k, $map($k)) }
)
Both of my examples return the key with the value incremented by 1.
The more "imperative" way would be to get a list of all keys, loop over them and look up the values for each of them:
declare namespace map="http://www.w3.org/2005/xpath-functions/map";
let $x := map{'a':1, 'b':2}
for $key in map:keys($x)
return $key || $x($key) + 1
A more elegant, functional approach would be to use map:for-each to "map" a (possibly anonymous) function onto the map (this is the term in functional programming when applying a function to each value of a list or sequence):
declare namespace map="http://www.w3.org/2005/xpath-functions/map";
let $x := map{'a':1, 'b':2}
return map:for-each($x, function($key, $value) { $key || $value + 1 })
Unlike for most other XQuery features and functions, the BaseX documentation (which usually limits to BaseX-specific contents) offers a rather easy to read and understand reference and tutorial on the XQuery map feature.

Order $each by name

I am trying to figure why my ajax $each alters the way my list of names gets printed?
I have an json string like this:
[{"name":"Adam","len":1,"cid":2},{"name":"Bo","len":1,"cid":1},{"name":"Bob","len":1,"cid":3},{"name":"Chris","len":1,"cid":7},{"name":"James","len":1,"cid":5},{"name":"Michael","len":1,"cid":6},{"name":"Nick","len":1,"cid":4},{"name":"OJ","len":1,"cid":8}]
Here all the names are sorted in alphabetic order, but when getting them out they are sorted by "cid"? Why, and how can I change this?
Here is my jQuery:
var names = {};
$.getJSON('http://mypage.com/json/names.php', function(data){
$.each(data.courses, function (k, vali) {
names[vali.cid] = vali.name;
});
});
I guess its because "names[vali.cid]", but I need that part to stay that way. Can it still be done?
Hoping for help and thanks in advance :-.)
Ordering inside an object is not really defined or predictable when you iterate over it. I would suggest sorting the array based on an internal property:
var names = [];
$.getJSON('http://mypage.com/json/names.php', function(data){
$.each(data.courses, function (k, vali) {
names.push({name: vali.name, cid: vali.cid});
});
names.sort(function(a, b) {
return a.name.localeCompare(b.name);
});
});
Now you have an array that is ordered and you can iterate over it in a predictable order as well.
There is no "ajax $each" - you probably mean the jQuery function.
With "when getting them out" I presume you mean something like console.debug(names) after your $each call
Objects aren't ordered in javascript per definition, so there is no more order in your variable "names". Still, most javascript implementations today (and all the ones probably important to you - the ones used in the most used browsers) employ a stable order in objects which normally depends on the order you insert stuff.
All this said, there can probably be 3 reasons you're not getting what you're expecting:
Try console.debug(data) and see what you get - the order as you want it?
As you don't explicitly state how you debug your stuff, the problem could be in the way you output and not the data is stored. Here too try console.debug(names).
You're using a function which dereferences on expection, like console.*. This means if you console.debug() an object, the displayed values will depend on the moment you unfold the displayed tree in your browser, not when the line was called!

Resources