Type preservation failing with RemoteClass - actionscript-3

Consider the following example dataclass:
[RemoteClass]
public class SOTestData {
public var i:int;
public function SOTestData(i:int) {
this.i = i;
}
}
As I understand, the RemoteClass metadata-tag should ensure that when an object of this class gets sreialized, the type information is preserved.
I used the following program to test:
public class SOTest extends Sprite {
public function SOTest() {
var data:SharedObject = SharedObject.getLocal("SOTest");
if (data.data.object) {
try {
var stored:SOTestData = data.data.object;
trace(stored.i);
} finally {
data.clear();
}
}
else {
data.data.object = new SOTestData(15);
data.flush();
}
}
}
Here the first run writes the data, seconds reads and clears. Running this, I still get a class cast error. Indeed, in the SharedObject there is no type information stored.
I don't think i'm using the metadata wrong, could it maybe be that the compiler doesn't know what to do with it? I don't get any compiler errors/warnings, although when i use some inexistant tag it doesn't complain either. I'm using Flex 4.6 SDK with FlashDevelop as IDE.
EDIT:
Below is the shared object. As you can see, the type is saved as "Object" instead of the actual type.
so = [object #2, class 'SharedObject'] {
data: [object #0, class 'Object'] {
object: [object #1, class 'Object', dynamic 'False', externalizable 'False'] {
i: 15,
},
}
}

I've only used RemoteClass for making AMF RemoteObject calls; I didn't think it had anything to do w/ Shared Objects. Per the docs
Use the [RemoteClass] metadata tag to register the class with Flex so
that Flex preserves type information when a class instance is
serialized by using Action Message Format (AMF). You insert the
[RemoteClass] metadata tag before an ActionScript class definition.
The [RemoteClass] metadata tag has the following syntax:
As best I can tell from the code you provided, you are not serializing the object in AMF format.
I believe your class cast error is due to the fact that you aren't casting your class. Shared Objects always come back as generic Objects. Try this:
var stored:SOTestData = data.data.object as SOTestData ;
Here is some code from an application I use. First the value object which will get serialized in a shared object:
package com.login.vos
{
[RemoteClass(alias="com.login.vos.UserVO")]
public class UserVO
{
public function UserVO()
{
}
public var firstName :String;
public var lastName :String;
public var userID :Number;
}
}
The the code to save the object:
public static function saveUserVO(userVO:UserVO):void{
var userSharedObject :SharedObject = SharedObject.getLocal('userVO') ;
userSharedObject.data.userVO = userVO;
userSharedObject.flush();
}
And finally, the code to load the objecT:
public static function getUserVO():UserVO{
var userSharedObject :SharedObject = SharedObject.getLocal('userVO')
if(userSharedObject.size <=0){
return null;
}
return userSharedObject.data.userVO as UserVO;
}
The only obvious difference between this and the code by the original poster is that I'm specifying an alias in the RemoteClass metadata.

Related

How can I solve 'Duplicate Constructor' error in Haxe?

In Haxe, I created a class named MyClass like:
class MyClass {
var score: String;
public function new (score: Int) {
this.score = Std.string(score);
}
public function new (score: String) {
this.score = score;
}
}
I need multiple constructors but Haxe does not allow me to do. It throws this error from building phase:
*.hx:*: lines * : Duplicate constructor
The terminal process terminated with exit code: 1
How can I solve this problem?
This is known as method overloading, which is not supported by Haxe apart from externs (but might be in the future). There's multiple ways you could work around this.
A common workaround in the case of constructors would be to have a static "factory method" for the second constructor:
class MyClass {
var score:String;
public function new(score:String) {
this.score = score;
}
public static function fromInt(score:Int):MyClass {
return new MyClass(Std.string(score));
}
}
You could also have a single constructor that accepts both kinds of arguments:
class MyClass {
var score:String;
public function new(score:haxe.extern.EitherType<String, Int>) {
// technically there's no need for an if-else in this particular case, since there's
// no harm in calling `Std.string()` on something that's already a string
if (Std.is(score, String)) {
this.score = score;
} else {
this.score = Std.string(score);
}
}
}
However, I wouldn't recommend this approach, haxe.extern.EitherType is essentially Dynamic under the hood, which is bad for type safety and performance. Also, EitherType is technically only intended to be used on externs.
A more type-safe, but also slightly more verbose option would be haxe.ds.Either<String, Int>. Here you'd have to explicitly call the enum constructors: new MyClass(Left("100")) / new MyClass(Right(100)), and then use pattern matching to extract the value.
An abstract type that supports implicit conversions from String and Int might also be an option:
class Test {
static function main() {
var s1:Score = "100";
var s2:Score = 100;
}
}
abstract Score(String) from String {
#:from static function fromInt(i:Int):Score {
return Std.string(i);
}
}
Finally, there's also an experimental library that adds overloading support with macros, but I'm not sure if it supports constructors.
I recommend to use type parameter
class MyClass<T> {
var score:String;
public function new(score:T) {
this.score = Std.string(score);
}
}
You can also use type parameter at constructor
class MyClass {
var score:String;
public function new<T>(score:T) {
this.score = Std.string(score);
}
}
However, T used at constructor fails at runtime (CS and Java), it is not fixed yet (Haxe 4). Otherwise, you could do this
class MyClass {
var score:String;
#:generic public function new<#:const T>(score:T) {
this.score = Std.is(T, String) ? untyped score : Std.string(score);
}
}
which nicely produce code like this (CS)
__hx_this.score = ( (( T is string )) ? (score) : (global::Std.#string(score)) );
causing Std.string() to be called only if T is not a String.
Hej,
With a simple example as it is, you can just do something like that function new( ?s : String, ?n : Int ){} and Haxe will use the correct argument by type. But you'll be able to do new() and maybe you don't want.

Json property alias in typescript

Let's say I want to get a data from Visual Studio TFS and the response (as json) is in this kind of format:
{
"Microsoft.VSTS.Scheduling.StoryPoints": 3.0,
// ......
}
There's dot in the property name. Reading from other questions I found out that I can read that json in typescript by using an interface like this
export interface IStory { // I don't think this kind of interface do me any help
"Microsoft.VSTS.Scheduling.StoryPoints": number
}
And then I can use the property with this syntax:
var story = GetStoryFromTFS();
console.log(story["Microsoft.VSTS.Scheduling.StoryPoints"]);
But I'd prefer not to call the property like this, since the intellisense won't able to help me finding which property I want to use (because I call the property using a string).
In C# there is a JsonProperty attribute which enable me to create a model like this:
public class Story
{
[JsonProperty(PropertyName = "Microsoft.VSTS.Scheduling.StoryPoints")]
public double StoryPoints { get; set; }
}
And then I can use the property this way:
var story = GetStoryFromTFS();
Console.WriteLine(story.StoryPoints);
This way the intellisense will able to help me finding which property I want to use.
Is there something like JsonProperty attribute in typescript? Or is there any other, better way, to achieve this in typescript?
You have many options. Just keep in mind that all of these options require you to pass the original data to the class that will access it.
Map the values.
class StoryMap {
constructor(data: IStory) {
this.StoryPoints = data["Microsoft.VSTS.Scheduling.StoryPoints"];
}
StoryPoints: number;
}
Wrap the data.
class StoryWrap {
constructor(private data: IStory) {}
get StoryPoints(): number { return this.data["Microsoft.VSTS.Scheduling.StoryPoints"] };
}
Build a decorator to map the data.
function JsonProperty(name: string) {
return function DoJsonProperty(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.get = function () {
return this.data[name];
}
descriptor.set = function (value) {
this.data[name] = value;
}
}
}
class StoryDecorator
{
constructor(private data: IStory) {}
#JsonProperty("Microsoft.VSTS.Scheduling.StoryPoints")
get StoryPoints(): number { return 0 };
}

Custom types in Navigation parameters in v3

In v3 if I wanted to pass two objects to another viewmodel:
public class Dog
{
}
public class Cat
{
}
var dog = new Dog();
var cat = new Cat();
ShowViewModel<SomeViewModel>(new {Dog = dog, Cat = cat });
public class SomeViewModel
{
Init(Dog dog, Cat cat)
{
}
}
As far as I can tell that won't work because the types aren't recognized and can't be stuck in a dictionary. If I wanted to have these serialized as json, passed to the view model, and deserialized as Init parameters, would I implement IExtraParser? And if that is correct, how do I go about adding the implementations to the ExtraParsers dictionary?
update:
This seems to do it:
var foo = Mvx.Resolve<IMvxFillableStringToTypeParser>();
foo.ExtraParsers.Add(new MyParser());
The default navigation mechanism in MvvmCross is deliberately lightweight.
It is really there to allow you to pass just one simple, serializable object - e.g.
public class DogNav
{
public int Id {get;set;}
public string Caption {get;set;}
}
// received in:
public class DogViewModel : MvxViewModel
{
public void Init(DogNav dogNav)
{
}
}
With this setup, if a navigation is triggered like:
// navigation
ShowViewModel<DogViewModel>(new DogNav() { Id=12, Caption="Good boy" });
then the underlying system will transport the values from that DogNav object - possibly using Uris, Intents or other serialization techniques - to the new DogViewModel and will then ensure Init is called with the correct values.
Because of the serialization, it's important:
not to pass big objects (Uris on WindowsPhone can break above a few hundred characters)
not to expect the same object instance to arrive - i.e. if you are using database-backed or stateful objects, then it's best to pass some kind of lookup key rather than the objects themselves.
not to expect that only one ViewModel will receive the message - on some operating systems, it may be that the user goes back and forth many, many times between apps causing many Views and ViewModels to be created.
not to expect that a ViewModel that receives the message is in the same process and memory space as the ViewModel that sent the request - the same may actually be received days later after a tombstoning event.
If you do want to pass multiple objects via navigation, then I think you can do this using code like:
public class CatNav
{
public int CatId {get;set;}
public string CatCaption {get;set;}
}
public class DogNav
{
public int DogId {get;set;}
public string DogCaption {get;set;}
}
// received in:
public class CatAndDogViewModel : MvxViewModel
{
public void Init(DogNav dogNav)
{
}
public void Init(CatNav catNav)
{
}
}
In this case you could navigate using:
var catNav = new CatNav() { CatId =12, CatCaption="Meow" };
var dogNav = new DogNav() { DogId =12, DogCaption="Woof" };
var bundle = new MvxBundle();
bundle.Write(catNav);
bundle.Write(dogNav);
ShowViewModel<CatAndDogViewModel>(bundle);
I think this would work...
However... please be aware that the serialization is very simple - so if CatNav and DogNav were to share a property name, then this would lead to problems - you'd end up with some Cags and Dots
Because of the Cag and Dot problem I don't recommend this approach...
If you do need more complex transitions in your apps, then one route is to:
UPDATE - see Passing complex navigation parameters with MvvmCross ShowViewModel
1. Add the Json Plugin (or any Json serializer) and change your Setup.cs code to create a MvxJsonNavigationSerializer - overriding CreateNavigationSerializer
protected override IMvxNavigationSerializer CreateNavigationSerializer()
{
return new MvxJsonNavigationSerializer();
}
Use a composite object in navigation like:
public class DogAndCatNav
{
public DogNav DogNav {get;set;}
public CatNav CatNav {get;set;}
}
This would be received by:
public void Init(DogAndCatNav dogAndCatNav)
{
}
But note that this technique does need a more powerful serialization engine - such as Json.
Overall... even after writing all this... I'd recommend you pass as little data as possible in your navigations!

Using an object literal as the default property value for an ActionScript 3 class

If I am using an object literal as the default value for a property in ActionScript, will this create a single object which is referenced by all instances, or a new object for each instances?
For example, if I created multiple instances of this AddressBook...
class AddressBook {
private var numbersByName:Object = {};
public function addNumber(name:String, number:String):void {
numbersByName[name] = number;
}
public function getNumber(name:String):String {
return numbersByName[name];
}
...would they behave properly, or would they use the same numbersByName object, causing all changes in one also apply to the others?
Even if each AddressBook instance does get its own object, it's conceivable that Flash might only apply this behaviour shallowly. Consider a default of nested object literals:
class AddressBook {
private var indicies:Object = {
numberToName: {},
nameToNumber: {}
};
public function addNumber(name:String, number:String):void {
indicies.numberToName[number] = name;
indicies.nameToNumber[name] = number;
}
public function getName(number:String):String {
return indicies.numberToName[number];
}
public function getNumber(number:String):String {
return indicies.nameToNumber[number];
}
}
Is the entire nested literal created anew for each instance, or do the different indicies objects share the same numberToName objects?
Generally, will there be a between the properties of a class whose property has an object literal default...
class MyClass {
private var myProperty:Object = { a: [1, 2, 3], b: { d: 4, e: 5 } };
}
...and one which initializes it in the constructor?
class MyClass {
private var myProperty:Object;
public function MyClass() {
myProperty = { a: [1, 2, 3], b: { d: 4, e: 5 } };
}
}
When you create an instance of your class, it will have its own unique variables.
So
var class_a:AddressBook = new AddressBook();
var class_b:AddressBook = new AddressBook();
class_a.var = 1;
class_b.var = 2;
trace(class_a.var) //traces 1.
Completely new objects will be created, none will be re-used. You get the same result whether you use the simple AddressBook or the one with nested object literals:
trace("None instantiated yet.")
var a:* = new AddressBook;
trace("A instantiated.");
var b:* = new AddressBook;
trace("B instantiated.");
a.addNumber("John", "555-5555");
trace(a.getNumber("John")); // prints 555-5555
trace(b.getNumber("John")); // prints null
Set a breakpoint inside of the object literal. What is the output when it breaks the first time?
None instantiated yet.
The second time?
None instantiated yet.
A instantiated.
The object literal is evaluated each time an instance is created, and at no other time.
Unfortunately, I was unable to find a definitive reference for this behaviour. The ActionScript "specification" doesn't contain enough detail. Better references would be appreciated.

Restoring custom class object array from SharedObject

I have an array of Widgets (a class I created) called "widgetArray" that I save into a shared object.
savedGame1.data.widgetArray = widgetArray;
When I go to load this data out and use the widgets I run into problems. The array is stored fine, but each widget is saved as an object. So I can't simply do:
widetArray = savedGame1.data.widgetArray;
because then if I try to use the widgets:
widgetArray[0].someWidgetFunction();
it does not work because flash thinks they are objects. I have tried typecasting the widgets when I load them, but it only produces a null object.
widgetArray[i] = widgetArray[i] as Widget;//widgetArray[i] becomes null
tempWidget:Widget = widgetArray[i] as Widget;//null as well
This question is asked in one of the forums from a friend, since there were no responses, i thought this is the right place for it...
How does anyone else deal with this?
Make two methods that can save and load a widget, the save method should take the data from the widget class and save it into the shared object the load class will take the data from the shared object and set the properties of the object.
Something like this :
public function save():Object
{
return {
"x":this.x,
"y":this.y,
"otherStuff":otherStuff
};
}
public function load(data:Object):void
{
this.x = data.x;
this.y = data.y;
this.otherStuff = data.otherStuff;
}
You can call the save method and store the results in an array and then store it in the shared object. You only need to save the data that is required to rebuild the widget class, not all the properties of the class.
Edit : Updated based on BlueRaja's comment.
As BlueRaja pointed out IExternalizable is meant to be used for this.
If you have a class like this :
public class MyClass implements IExternalizable
{
private var one:int = 1;
private var two:int = 2;
public function MyClass()
{
}
public function writeExternal(output:IDataOutput):void
{
output.writeInt(one);
output.writeInt(two);
}
public function readExternal(input:IDataInput):void
{
one = input.readInt();
two = input.readInt();
}
public function print():void
{
trace(one);
trace(two);
}
}
Then you can save it like this:
registerClassAlias("MyClass", MyClass);
var sharedObject:SharedObject = SharedObject.getLocal("so");
var myClass:MyClass = new MyClass();
sharedObject.data['storedObject'] = myClass;
sharedObject.flush();
Then to load it :
registerClassAlias("MyClass", MyClass);
var sharedObject:SharedObject = SharedObject.getLocal("so");
var loadedClass:MyClass = sharedObject.data['storedObject'] as MyClass;
loadedClass.print();
Hope that helps.
http://tush.wordpress.com/2007/07/08/actionscript-3-serializing-classes-using-registerclassalias/ ... This is also a genuine way to store the custom class objects in shared objects