type check dynamic properties flowtype - flowtype

I am trying to add dynamic properties to an object and have flow type check them:
my function would be like this:
function defineStuff(obj:MyType, keys:string[]):??? {
keys.forEach(function(key) {
Object.defineProperty(obj, key, {get:function(){....}});
obj["Add"+key] = function(value) {....};
obj["Remove"+key] = function(value) {....};
}
return obj;
}
I would like to be able to do stuff like this;
var obj : MyType = fetchMyObj();
defineStuff(obj, ["Thing", "OtherThing"]);
var thing = obj.Thing;
obj.AddOtherThing(10);
all dynamic properties type would be number
is there a syntax for doing this in flow? (i.e. how to fill the ???)

This should work for dictionaries.
type MyType = {[key: string]: number};
// ...
function defineStuff(obj: MyType, keys:string[]): MyType {
keys.forEach(function(key) {
Object.defineProperty(obj, key, {get:function(){....}});
// you can't have functions here, because you say that all values should be numbers
// obj["Add"+key] = function(value) {....};
// obj["Remove"+key] = function(value) {....};
}
return obj;
}
// ...
See docs

Related

Manipulate list of Flow generic type with optional id

I'd like to have a function that takes an array with objects that could have id property on them. The function will remove the first object that has id and it matches function's second argument.
Here is my stub:
// #flow
function removeFromArrayByObjectId<T: {id?: string}>(array: Array<T>, id: any): Array<T> {
for (let i = 0; i < array.length; i++) {
if (array[i].id && array[i].id === id) {
array.splice(i, 1);
break;
}
}
return array;
}
export {
removeFromArrayByObjectId,
};
When I'm passing arr defined as (below) I'm getting
type Obj = { id: string, value: string };
const arr: Array<Obj> = [obj1, obj2];
Cannot call removeFromArrayByObjectId with array literal bound to array because string [1] is incompatible with undefined [2] in property id of array element.Flow(InferError)
However, when I remove ? from id? it works fine. I'm expecting that some arrays will have elements without the id so would like it to be optional. Any suggestions?
The error is very similar to the one mentioned in Why can't `{ a: string }` flow into `{ a?: string }`. Essentially, Flow does not know that removeFromArrayByObjectId does not manipulate the elements of array (e.g., delete a property). You'll want to mark T as "read-only" so Flow knows that the function will not do so.
function removeFromArrayByObjectId<T: $ReadOnly<{id?: string}>>(array: Array<T>, id: any): Array<T> {
for (let i = 0; i < array.length; i++) {
if (array[i].id && array[i].id === id) {
array.splice(i, 1);
break;
}
}
return array;
}
type Obj = { id: string, value: string };
declare var obj1: Obj;
declare var obj2: Obj;
const arr: Array<Obj> = [obj1, obj2];
const modified_arr: Array<Obj> = removeFromArrayByObjectId(arr);
Try Flow

Meaning of interrogation

I'm a little bit confuse about the meaning difference of using "?"
I offen saw this:
var foo?: number = "bar"
But also saw this:
function foo(bar: {baz: ?string}) { ... }
And also saw both together.
I've read about invariants and maybe types, but if I understood it right, both signals have the same meaning, which is: "this type is of kind 'X', but it maybe is null or undefined".
Is it right or am I getting it wrong?
Here are answers to most of your questions:
// Don't know what this is, or why you would use it
// Error: undefined is incompatible with string
var foo1?: string = undefined;
// ?string means string, null, or undefined
var foo2: ?string = undefined;
type FooOptional = { foo?: string };
type FooMaybe = { foo: ?string };
// If it's optional it can be completely omitted
var foo3: FooOptional = {};
// It can also be explicitly set to undefined
var foo4: FooOptional = { foo: undefined };
// But not null!
var foo5: FooOptional = { foo: null };
// If it's a maybe type, it must be specified
// Error: property `foo` not found
var foo6: FooMaybe = {};
// But you can set it explicitly to null or undefined
var foo7: FooMaybe = { foo: null };
var foo8: FooMaybe = { foo: undefined };
(tryflow link)
Using both together (e.g. {foo?: ?string} as a type) usually (but not in all cases) indicates that the author doesn't quite know what type they want to use and have just added question marks until it typechecks. Typically I have found that if I think it through, it makes sense to use either an optional property or a maybe type, but not both.

How to get a property name and its value using Swift 2.0, and reflection?

Given this Model:
public class RSS2Feed {
public var channel: RSS2FeedChannel?
public init() {}
}
public class RSS2FeedChannel {
public var title: String?
public var description: String?
public init() {}
}
What would I need to do in order to get the property names and values of an RSS2FeedChannel instance?
Here's what I'm trying:
let feed = RSS2Feed()
feed.channel = RSS2FeedChannel()
feed.channel?.title = "The Channel Title"
let mirror = Mirror(reflecting: feed.channel)
mirror.children.first // ({Some "Some"}, {{Some "The Channel Title...
for (index, value) in mirror.children.enumerate() {
index // 0
value.label // "Some"
value.value // RSS2FeedChannel
}
Ultimately, I'm trying to create a Dictionary that matches the instance, using reflection, but so far I'm unable to get the properties name and values of the instance.
Documentation says that:
The optional label may be used when appropriate, e.g. to represent the name of a stored property or of an active enum case, and will be used for lookup when Strings are passed to the descendant method.
Yet I only get a "Some" string.
Also, the value property is returning a string with the Type RSS2FeedChannel when I would expect each children to be "An element of the reflected instance's structure."!
When i understand correct this should solve ur problem:
func aMethod() -> Void {
let feed = RSS2Feed()
feed.channel = RSS2FeedChannel()
feed.channel?.title = "The Channel Title"
// feed.channel?.description = "the description of your channel"
guard let channel = feed.channel else {
return
}
let mirror = Mirror(reflecting: channel)
for child in mirror.children {
guard let key = child.label else {
continue
}
let value = child.value
guard let result = self.unwrap(value) else {
continue
}
print("\(key): \(result)")
}
}
private func unwrap(subject: Any) -> Any? {
var value: Any?
let mirrored = Mirror(reflecting:subject)
if mirrored.displayStyle != .Optional {
value = subject
} else if let firstChild = mirrored.children.first {
value = firstChild.value
}
return value
}
just some little changes for swift 3:
private func unwrap(_ subject: Any) -> Any? {
var value: Any?
let mirrored = Mirror(reflecting:subject)
if mirrored.displayStyle != .optional {
value = subject
} else if let firstChild = mirrored.children.first {
value = firstChild.value
}
return value
}
You can use the descendent method on the Mirror object to get this information. It will return nil if the values aren't found or the optionals contain no value.
let mirror = Mirror(reflecting: feed.channel)
let child1 = mirror.descendant("Some", "title") // "The Channel Title"
// or on one line
let child3 = Mirror(reflecting: feed).descendant("channel", "Some", "title")

How can I get the name of a Kotlin property?

I have the following function to access a property's delegate. It uses Kotlin reflection to get a property's name and Java reflection to get the field.
fun Any.getDelegate<T>(prop: KProperty<T>): Any {
return javaClass.getDeclaredField("${prop.name}\$delegate").let {
it.setAccessible(true)
it.get(this)
}
}
The method is used like this:
val delegate = a.getDelegate(A::b)
However, I would prefer to use it like this:
val delegate = a.b.delegate
The problem with the code above is getting the property name of a.b and getting the instance a from a.b. From what I know about Kotlin, this is probably not possible, however I'd like to see if I can clean up my function at all.
To give a bigger picture of what I'm trying do here's my complete code. I want an observable delegate to which I can add and remove observers using the delegate reference and without creating addition variables.
fun Any.addObservable<T>(prop: KProperty<T>, observer: (T) -> Unit) {
getObservableProperty(prop).observers.add(observer)
}
fun Any.getObservableProperty<T>(prop: KProperty<T>): ObservableProperty<T> {
return getDelegate(prop) as ObservableProperty<T>
}
fun Any.getDelegate<T>(prop: KProperty<T>): Any {
return javaClass.getDeclaredField("${prop.name}\$delegate").let {
it.setAccessible(true)
it.get(this)
}
}
class ObservableProperty<T>(
initialValue: T,
initialObservers: Array<(T) -> Unit> = emptyArray()) : ReadWriteProperty<Any?, T> {
private var value = initialValue
public val observers: MutableSet<(T) -> Unit> = initialObservers.toHashSet()
public override fun get(thisRef: Any?, desc: PropertyMetadata): T {
return value
}
public override fun set(thisRef: Any?, desc: PropertyMetadata, value: T) {
this.value = value
observers.forEach { it(value) }
}
}
class A() {
var b by ObservableProperty(0)
}
fun main(args: Array<String>) {
val a = A()
a.addObservable(A::b) {
println("b is now $it")
}
a.b = 1
a.b = 2
a.b = 3
}
Edit:
I just realized that the function also isn't strict because the property delegate field name is referenced by KProperty name, which doesn't require a strong reference to the enclosing class. Here's an example to demonstrate the problem:
class A() {
var foo by ObservableProperty(0)
}
class B() {
var foo by ObservableProperty(0)
}
fun main(args: Array<String>) {
val a = A()
a.addObservable(B::foo) {
println("b is now $it")
}
a.foo = 1
a.foo = 2
a.foo = 3
}
This compiles and runs without error because A::foo and B::foo both result in a field string of "foo$delegate.
Right now reflection is all we can do to get to the delegate object. We are designing a language feature to have direct access to delegate instance, but it's long way to go.
This is how you get the name of a Kotlin Property (although only with an instance of the class). This part will be useful to anyone arriving at this question purely based off its title.
class Stuff(val thing: String)
val stuff = Stuff("cool stuff")
val thingFieldName = "${stuff.thing}\$delegate"
// value of thingFieldName is now "thing"
In terms of getting the delegate itself easier, they say you can now do this:
class Foo {
var bar: String by ReactiveProperty<String>()
}
val foo = Foo()
val bar = foo.bar
val barDelegate = ... // foo.bar$delegate
See ticket.

In Flex3, what type is an object property?

I have many Flex objects like this one:
public class MyData {
public var time: Date;
public var label: String;
}
I am populating this object from a DB record retrieved via AMF that looks something like this:
{
label: "Label",
incident: "2009-08-15 11:12:14.12233"
}
I want to write a generic value mapper for these object that, given a target object (instance of MyData here) and an input record, will be able to tell that MyData.time is a Date field and perform type mapping automatically. Something like this:
function map(obj, targetType): * {
var newInstance: * = new targetType();
for (var property: String in obj) {
if (getPropertyType(targetType, property) == Date) {
newInstance[property] = parseDate(obj[property]);
}
else {
newInstance[property] = obj[property];
}
}
}
function getPropertyType(type_var: Class, property: String): Class {
// .. this is what I have no idea how to do
}
Can someone fill in the blank here?
You possibly need something like describeType. And maybe you need to use getDefinitionByName() if you want to make to a real object. So something like this for the contents of your function:
var typeXml:XML = describeType(type_var[property]);
return getDefinitionByName(typeXml.type[0].#name);
I haven't compiled it. Just throwing it out there to see if it helps.
You can use the 'is' operator to check the type of an object.
The is operator
function map(obj, targetType): * {
var newInstance: * = new targetType();
for (var property: String in obj) {
if (obj[property] is Date) {
newInstance[property] = parseDate(obj[property]);
}
else {
newInstance[property] = obj[property];
}
}
}
hth
Koen
If you need to map an Object variable to a variable class as MyData you can do the following
public class MyData
{
public var time: Date;
public var label: String;
function map(obj:Object):void
{
for (var property: String in obj)
{
this[property] = obj[property];
}
}
}
Note: The object obj must contain the exact "time" and "label" properties.
Hope it solves your problem

Resources