as3 dictionary of arrays - actionscript-3

My code:
var dict = new Dictionary();
dict[[1, 2]] = 1;
var dbg = dict[[1, 2]];
trace(dbg);
Output:
undefined
Why?
How can I get
set<pair<int, int> >
from C++?

Note: I would not call [1,2] "pair of int", it is "array with 2 elements". { item1:1, item2:2 } would look more like pair to me.
According to documentation Dictionary uses strict equals (===) to compare keys, so passing new array as key every time will not work for indexing (arrays are strictly equal only when it is exactly the same array, not an array with the same content).
Depending on your needs converting key to a string maybe good option:
var keyPair = [1,2];
var key = keyPair[0] + "_" + keyPair[1];

Related

Jsony newHook has `SIGSEGV: Illegal storage access. (Attempt to read from nil?)` when deserializing into ref-objects

I am writing a web-application and am deserializing via jsony into norm-model-object types.
Norm-model-types are always ref objects. Somehow my code which is very similar to the default example in jsony's github documentation does not compile. Instead I receive the error SIGSEGV: Illegal storage access. (Attempt to read from nil?).
See here my code sample
import std/[typetraits, times]
import norm/[pragmas, model]
import jsony
const OUTPUT_TIME_FORMAT* = "yyyy-MM-dd'T'HH:mm:ss'.'ffffff'Z'"
type Character* {.tableName: "wikientries_character".} = ref object of Model
name*: string
creation_datetime*: DateTime
update_datetime*: DateTime
proc parseHook*(s: string, i: var int, v: var DateTime) =
##[ jsony-hook that is automatically called to convert a json-string to datetime
``s``: The full JSON string that needs to be serialized. Your type may only be a part of this
``i``: The index on the JSON string where the next section of it starts that needs to be serialized here
``v``: The variable to fill with a proper value]##
var str: string
s.parseHook(i, str)
v = parse(s, OUTPUT_TIME_FORMAT, utc())
proc newHook*(entry: var Character) =
let currentDateTime: DateTime = now()
entry.creation_datetime = currentDateTime # <-- This line is listed as the reason for the sigsev
entry.update_datetime = currentDateTime
entry.name = ""
var input = """ {"name":"Test"} """
let c = input.fromJson(Character)
I don't understand what the issue appears to be here, as the jsony-example on its github page looks pretty similar:
type
Foo5 = object
visible: string
id: string
proc newHook*(foo: var Foo5) =
# Populates the object before its fully deserialized.
foo.visible = "yes"
var s = """{"id":"123"}"""
var v = s.fromJson(Foo5)
doAssert v.id == "123"
doAssert v.visible == "yes"
How can I fix this?
The answer lies in the fact that norm-object-types are ref objects, not normal (value) objects (Thanks to ElegantBeef, Rika and Yardanico from the nim-discord to point this out)! If you do not explicitly 'create' a ref-type at one point, the memory for it is never allocated since the code doesn't do the memory allocation for you unlike with value types!
Therefore, you must initialize/create a ref-object first before you can use it, and Jsony does not take over initialization for you!
The correct way to write the above newHook thus looks like this:
proc newHook*(entry: var Character) =
entry = new(Character)
let currentDateTime: DateTime = now()
entry.creation_datetime = currentDateTime
entry.update_datetime = currentDateTime
entry.name = ""

Remove layers (keys) from heavily nested JSON in JSCRIPT/VBA

I am parsing a heavily nested JSON in VBA, using scriptcontrol/jscript.
The resulting JSON object is super nested, and has recurring 'useless' levels / layers called 'buckets'.
Is there a way I can remove these collectively from either my json string or the parsed json object?
Imagine it something like this:
responses.0.buckets.0.aggregations.0.10.buckets.0.5.buckets.0.9.buckets.0.20.buckets.0.8.buckets.0.13.buckets.0.14.buckets.0.15.buckets.0.16.buckets.0.19.buckets.0.18.buckets.0.21.doc_count_error_upper_bound
I'd only need the 'doc_count_error_upper_bound' value, and could essentially do without all the 0s and without all the buckets, making it less nested into:
responses.aggregations.10.5.9.20.8.13.14.15.16.19.18.21.doc_count_error_upper_bound
This would still be pretty heavily nested, but saves me a lot of headaches already.
I just do not know how I could do this with jscript/scriptcontrol in VBA (es3).
The source data is coming from a Kibana dashboard (examples on http://demo.elastic.co/ )
Thanks for any help!
Jasper
UPDATE:
Question regarding VBA code - the VBA code I have is irrelevant, as it's the standard way of loading a json string into an object via scriptcontrol.
I do not use EVAL, but for example purposes, it would be something like the below:
Dim Scr as Object, Json as Object
Set Scr = CreateObject("Scriptcontrol")
Scr.Language = "Jscript"
Set Json = Scr.Eval("(" & WinHTTP.ResponseText & ")")
I cannot share an example of the JSON string, as it contains sensitive data.
But ultimately, that's beside the question.
Consider example https://adobe.github.io/Spry/data/json/donuts.js
On the top there, is "batter" as key in between "batters" and the different IDs. If I'd want to remove that key, but keep the underlying ID data - how would I do that, through a js scrip that works in scriptcontrol in VBA?
UPDATE:
omegastripes answer worked very well, however, I failed to realize that a number of the keys I wanted to remove (the 'buckets' and '0' etc) had keys and values under them.
Let's take the example of the donuts, just altered a bit - see here:
https://pastebin.com/WxYir7vK
now I would want to remove the '0', '1', '2' etc keys without losing the underlying sub-keys.
However, for omegastripes code to work, I'd have to delete keys 'sequence', 'variant', 'name', and 'ppu' from all layers / throughout the json.
I can do that for one of them, for one layer with the function below:
function unseat(obj, prop) { for(var k in obj[prop]) obj[k] = obj[prop][k]; delete obj[prop]; return obj; }
And then calling the functio 'unseat (JSONObj, "variant")' - this works, but only for one of the four variables at a time and only for one layer.
How can I alter this so that I can remove it throughout the object, for all four at once, so that afterwards I can use omegastripes code to unwrap.
Summary
1) I take this json string: https://pastebin.com/WxYir7vK
2) parse it into script control into VBA
3) loop through it and remove all 'sequence', 'variant', 'name' and 'ppu' key/value pairs
4) unwrap it via omegastripes code.
Step 1 / 2 and 4 are taken care of - but how to do 3?
Thanks!
Using ScriptControl for parsing JSON has the following shortcomings (check this answer for details):
System environment is exposed to malware code injections received within response.
ScriptControl is not available on 64-bit MS Office.
Anyway if you are confident that operating in JScript environment is the only way, you may unwrap excessive nesting of objects and arrays structure using the below functions:
function gParse(sample) {
return eval('(' + sample + ')');
};
function gUnwrap(sample) {
for (var key in sample) {
sample[key] = gUnwrap(sample[key]);
};
var count = 0;
for (var key in sample) {
count++;
if (count == 2) break;
};
if (count == 1) {
var type = gGetType(sample);
if (type == 'Array' || type == 'Object') {
var type = gGetType(sample[key]);
if (type == 'Array' || type == 'Object') {
return sample[key];
}
}
};
return sample;
};
function gGetType(sample) {
return {}.toString.call(sample).slice(8, -1);
};
That could be done in VBA as shown below:
Option Explicit
Sub Test()
Dim sJSON As String
Dim ParseJSON As Object
Dim UnwrapJSON As Object
Dim oJSON As Object
With CreateObject("MSXML2.XMLHTTP")
.Open "GET", "https://adobe.github.io/Spry/data/json/donuts.js", False
.send
sJSON = .responseText
End With
With CreateObject("htmlfile")
With .parentWindow
.execScript "function gParse(sample) {return eval('(' + sample + ')')};"
.execScript "function gUnwrap(sample) {for (var key in sample) {sample[key] = gUnwrap(sample[key]);}; var count = 0; for (var key in sample) {count++; if (count == 2) break;}; if (count == 1) {var type = gGetType(sample); if (type == 'Array' || type == 'Object') {var type = gGetType(sample[key]); if (type == 'Array' || type == 'Object') {return sample[key];}}}; return sample;};"
.execScript "function gGetType(sample) {return {}.toString.call(sample).slice(8, -1)};"
Set ParseJSON = .gParse
Set UnwrapJSON = .gUnwrap
End With
End With
Set oJSON = UnwrapJSON(ParseJSON(sJSON))
End Sub
The locals window shows JSON object for the sample you provided as follows:
And unwrapped JSON object:

AS3 1067: Implicit coercion of a value of type Number to an unrelated type String

I am getting error: "1067: Implicit coercion of a value of type Number to an unrelated type String."
When attempting to run:
var cam_array: Array = Camera.names;
for (var ci:Number=0; ci < cam_array.length; ci++){
trace(Camera.getCamera(ci).name);
}
yet when I run:
var mic_array: Array = Microphone.names;
for (var mi:Number=0; mi < mic_array.length; mi++){
trace(Microphone.getMicrophone(mi).name);
}
there is no issue.
The only change is from Microphone to Camera, so what gives?
Thanks in advance!
Camera function getCamera() accepts an optional parameter of name:
name:String (default = null) — Specifies which camera to get, as
determined from the array returned by the names property. For most
applications, get the default camera by omitting this parameter. To
specify a value for this parameter, use the string representation of
the zero-based index position within the Camera.names array. For
example, to specify the third camera in the array, use
Camera.getCamera("2").
If you do not want the default parameter, cast the index position to a string:
var cam_array:Array = Camera.names;
for (var ci:uint = 0; ci < cam_array.length; ci++){
trace(Camera.getCamera(ci.toString()).name);
}
Another method to loop camera names would be:
var cam_array:Array = Camera.names;
for each (var camera:String in cam_array) {
trace(camera);
}
On the other hand, Microphone function getMicrophone() accepts an optional parameter of index:
index:int (default = -1) — The index value of the microphone.

Why does iterating over a generic object in ActionScript give me values instead of keys?

I may be doing something really stupid, but I don't get why the below code doesn't work...
Here, I create a generic Object called spellbook:
// A list of all the player's spells
public var spellBook:Object = {};
Here, I add a key-value pair to the spellbook:
spellBook["bubble"] = new BubbleSpell(spellBook);
And here I try to output the contents of the spellbook:
trace("Spells initialised. Available spells are:");
for each (var _spell:String in spellBook)
{
trace(" ", _spell, " : ", spellBook[_spell]);
}
But this is the output I get:
Spells initialised. Available spells are:
[object BubbleSpell] : undefined
What I don't get is why it's not outputting:
Spells initialised. Available spells are:
bubble : [object BubbleSpell]
??
It's as if I'm iterating over the values in spellbook, rather than the keys... is that what's happening? All the docs I've seen so far seem to indicate that this is the correct way to iterate over a dictionary (which a generic object kind of is...) Do I need to call a method to get keys instead of values for generic objects?
So confused!
for each is used to iterate over values, you want to use a for loop which iterates over keys, eg:
var mySpells = {
speedy: new SpeedySpell(),
sleepy: new SleepySpell()
};
for (var key : String in mySpells) {
trace("key: " + key + " value: " + mySpells[key]);
}
Note that when iterating over the keys in a Dictionary you should leave the key value untyped (*) as a Dictionary's keys can be of any type (whereas an Object's keys can only be of type String).
A common trick would be to create a utility function which extracts an Object's keys to an Array for easier iteration:
function getKeys(source : Object) : Array {
const result : Array = [];
for (var key : * in source) {
result.push(key);
}
return result;
}
You can then make use of this helper function:
trace(getKeys(mySpells));
Because you're using a for each loop. Use a for loop instead:
for (var key:* in spellBook) {
trace(key + ': ' + spellBook[key]);
}

ActionScript: Is there ever a good reason to use 'as' casting?

From what I understand of ActionScript, there are two kinds of casts:
var bar0:Bar = someObj as Bar; // "as" casting
var bar1:Bar = Bar(someObj); // "class name" casting (for want of a better name)
Also, and please correct me if I'm wrong here, as casting will either return an instance of the class or null, while "class name" casting will either return an instance of the class or raise an exception if the cast is impossible – other than this, they are identical.
Given this, though, as casting seems to be a massive violation of the fail-fast-fail-early principle... And I'm having trouble imagining a situation where it would be preferable to use an as cast rather than a class name cast (with, possibly, an instanceof thrown in there).
So, my question is: under what circumstances would it be preferable to use as casting?
There are a couple of points in this discussion worth noting.
There is a major difference in how the two work, Class() will attempt to cast the object to the specified Class, but on failure to do so will (sometimes, depends on datatype) throw a runtime error. On the other hand using object as Class will preform a type check first, and if the specified object cannot be cast to the indicated Class a null value is returned instead.
This is a very important difference, and is a useful tool in development. It allows us to do the following:
var o:MyClass = myArray[i] as MyClass;
if(o)
{
//do stuff
}
I think the usefulness of that is pretty obvious.
"as" is also more consistent with the rest of the language (ie: "myObject is MyClass").
The MyClass() method has additional benefits when working with simple data types (int, Number, uint, string) Some examples of this are:
var s:String = "89567";
var s2:String = "89 cat";
var n:Number = 1.9897;
var i:int = int(s); // i is = 89567, cast works
var i2:int = int(s2); //Can't convert so i2 is set to 0
var i3:int = int(n); // i = 1
var n2:Number = Number(s2); // fails, n2 = NaN
//when used in equations you'll get very different results
var result:int = int(n) * 10; //result is 10
var result:int = n * 10; //result is 19.89700
var result:int = int(s2) * 10; //result is 0
trace(s2 as Number); //outputs null
trace(s2 as int); //outputs null
trace(Number(s2)); //outputs NaN
This is a good and important topic, as a general rule I use "as" when working with Objects and Cast() when using simpler data types, but that's just how I like to structure my code.
You need to use as to cast in two scenarios: casting to a Date, and casting to an Array.
For dates, a call to Date(xxx) behaves the same as new Date().toString().
For arrays, a call to Array(xxx) will create an Array with one element: xxx.
The Class() casting method has been shown to be faster than as casting, so it may be preferable to as when efficiency matters (and when not working with Dates and Arrays).
import flash.utils.*;
var d = Date( 1 );
trace( "'" + d, "'is type of: ",getQualifiedClassName( d ) );
var a:Array = Array( d );
trace( "'" + a, "' is type of: ", getQualifiedClassName( a ) );
//OUTPUT
//'Mon Jun 15 12:12:14 GMT-0400 2009 'is type of: String
//'Mon Jun 15 12:12:14 GMT-0400 2009 ' is type of: Array
//COMPILER ERRORS/WARNINGS:
//Warning: 3575: Date(x) behaves the same as new Date().toString().
//To cast a value to type Date use "x as Date" instead of Date(x).
//Warning: 1112: Array(x) behaves the same as new Array(x).
//To cast a value to type Array use the expression x as Array instead of Array(x).
`
They actually do different things...when you say
myvar as ClassName
You are really just letting the compiler know that this object is either a ClassName or a subclass of ClassName
when you say:
ClassName(myvar)
It actually tries to convert it to that type of object.
so if your object is a or a descent of the class and you do not need to convert it you would use as
examples:
var myvar:String = '<data/>';
var othervar:XML = XML(myvar); //right
var myvar:String = '<data/>';
var othervar:XML = (myvar as XML); //wrong
var myvar:XML = <data/>;
var othervar:XML = myvar as XML; // right
Use 'as' with arrays.
var badArray:Array;
badArray = Array(obj);
Will yield an array of length one with the original array in the first element. If you use 'as' as follows, you get the exptected result.
var goodArray:Array;
goodArray = obj as Array;
Generally, 'as' is preferable to 'Class()' in ActionScript as it behaves more like casting in other languages.
I use it when I have an ArrayCollection of objects and need to enumerate through them, or use a selector function.
e.g.
var abc:mytype = mycollection.getItemAt(i) as mytype