How can I return an obj of a class parameter in a func in Echoes


(Jon Nermut) #1

I have a decode method, which decodes Json and returns a certain type of class thats passed in:

/// Service for parsing and serializing Json
public protocol JsonService
{
    /// Decodes a top-level value of the given type from the given JSON representation.
    ///
    /// - parameter type: The type of the value to decode.
    /// - parameter data: The data to decode from.
    /// - returns: A value of the requested type.
    /// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not valid JSON.
    /// - throws: An error if any value throws an error during decoding.
    #if os(iOS) || os(OSX)
    func decode<T>(_ type: T.Type, from bytes: Bytes) throws -> T where T : Decodable
    #endif
    #if NET
    func decode<T>(_ type: System.Type, from bytes: Bytes) throws -> T 
    #endif
    #if ANDROID
    func decode<T>(_ type: java.lang.Class<T>, from bytes: Bytes) throws -> T
    #endif

}

This is used like:

 let myObj: MyObject = jsonService.decode( MyObject.self, someJsonBytes)

As you can see I have to specialise the method signature for XCode, Cooper/Java & Echoes/.Net, as T.Type isnt valid in Silver.

This works well in XCode & Cooper/Java, but in Echoes/.Net I have problems because System.Type is not generic, like the other two.
The only way I can get this to work in Echose/.Net is to call like
jsonService.decode<MyObject>( MyObject.self, someJsonBytes)

Which would be ok, except that XCode hates this, and gives an error at this syntax:

So - looking for ideas or tricks how I can get the .Net/Echoes version working the same as the others, where the generic return type is specified by the type passed as the first param.
I guess worst case is I have to return Any, and force cast everywhere in client code. But ugly!


(Jon Nermut) #2

Best I can do that works across platform with one syntax is:

Cooper

class Decoder<T>
{
    var c: Class<T>
    init(_ type: Class<T>)
    {
        self.c = type
    }

    func decode() -> T
    {
        print(c)
        return c.newInstance() as! T
    }
}

class MyClass
{}

let my: MyClass = Decoder<MyClass>(MyClass.self).decode()
print(my)

Echoes

class Decoder<T>
{
    var c: System.Type
    init(_ type: System.Type)
    {
        self.c = type
    }

    func decode() -> T
    {
        print(c)
        return c.GetConstructors()[0].Invoke([]) as! T
    }
}

class MyClass
{}

let my: MyClass = Decoder<MyClass>(MyClass.self).decode()
print(my)

Which is ok I guess except for the verbosesness of the call: Decoder<MyClass>(MyClass.self).decode()


(RemObjects) #3

Thanks, logged as bugs://80965


(marc hoffman) #4

This sounds like something we need to add proper support for.

As a workaround, if you use Elements for all platforms and use Elements RTL, there’s a cross-platform Type type you can use (similar to System.Type, except it is mapped properly on each platform, so you don’t need to ifdef).


(Carlo Kok) #5

you could add an inline function that passes the type via typeof:

class Decoder<T>
{
    var c: System.Type
    init(_ type: System.Type)
    {
        self.c = type
    }

    func decode() -> T
    {
        print(c)
        return c.GetConstructors()[0].Invoke([]) as! T
    }
  @inline(__always) static func decode() -> T
  {
    return Decoder<T>(T.self).decode()
  }
}
Decoder<MyClass>.decode()

(Jon Nermut) #6

Thanks Carlo, thats a cool technique of getting T that I didnt know about. Even works under Java type erasure!


(Carlo Kok) #7

You happy with that approach? If so I’ll close this task.


(Jon Nermut) #8

I’ll use that idiom for the time being, for sure.
I would be good if you could support T.Type in the long term though, as it’s standard swift, as in

func decode<T>(_ type: T.Type, from bytes: Bytes) throws -> T

My function is basically a polyfill of:
https://developer.apple.com/documentation/foundation/jsondecoder/2895189-decode

cheers

Jon