0

I'm quite new to Typescript and have a bit of a problem to get my code working. I have the following interface/class structure

interface IInterface {
  id : number;
  method() : string;
}

class IClass implements IInterface {
  id : number;
  method() : string { return "foo";}
}

Now I want to get some data from a webservice via the following call

$.get("/some/url", (data : Array<IInterface>) => {
    for (var i = 0; i < data.length; i++) {
        console.log(data[i].id);
        console.log(data[i].method());
    }
});

While this compiles perfectly fine in typescript and also all properties are set perfectly fine, I get a runtime TypeError data[i].method is not a function

So now my question: How can I cast/assign (?) this correctly, so that the methods are also available in the resulting JavaScript?

UPDATE As requested: a dump of the data I get from the webservice.

data = [{id : 1}, {id : 2}, ...]  

Of course, this is a simplified example, the real classes/interfaces have some more properties (they all are assigned correctly), but also only one method (so far, when I get it working, some more will follow).

derpirscher
  • 14,418
  • 3
  • 18
  • 35

2 Answers2

1

This is a common problem of converting JSON objects into instances of the classes. There are some proposals discussed here: JSON to TypeScript class instance?

Below is the solution I came up to do such deserialization:

export class Helper
{
    public static DESCRIPTOR_SIGN = '$';

    private static m_entityModules = [];

    private static ReviveDateTime(key: any, value: any): any 
    {
        if (typeof value === 'string')
        {
            let a = /\/Date\((\d*)\)\//.exec(value);
            if (a)
            {
                return new Date(+a[1]);
            }
        }

        return value;
    }

    private static RessurectValue(json: any, environments: any[]): any
    {
        if(json == null)
        {
            return null;
        }
        else if(Helper.IsString(json))
        {
            return json;
        }
        else if(json instanceof Date)
        {
            return json;
        }
        else if(typeof json === 'object') 
        {
            return Helper.RessurectInternal(json, environments);
        } 
        else 
        {
            return json;
        }
    }

    private static RessurectInternal(json: any, environments: any[]): any 
    {
        var instance;

        if(!json[Helper.DESCRIPTOR_SIGN])
        {
            instance = {};
        }
        else
        {
            instance = Helper.CreateObject(json[Helper.DESCRIPTOR_SIGN]);

            if(Helper.IsUndefined(instance))
            {
                throw new Error('Unknown type to deserialize:' + json[Helper.DESCRIPTOR_SIGN]);
            }              
        }

        for(var prop in json) 
        {
            if(!json.hasOwnProperty(prop) || prop === Helper.DESCRIPTOR_SIGN) 
            {
                continue;
            }

            let val = json[prop];
            instance[prop] = Helper.Ressurect(val, environments);
        }

        return instance;
    }

    private static CreateObject(className: string, environments?: any[]): any
    {
        let instance: any;
        for(let e of environments)
        {
            var construct = e[className];

            if(!Helper.IsUndefined(construct))
            {
                instance = new construct(); 
                break;
            }
        }

        return instance;
    }

    private static IsNumber(val: any): boolean
    {
        return typeof val == 'number' || val instanceof Number;
    }

    private static IsUndefined(val: any): boolean
    {
        return typeof(val) === 'undefined';
    }

    private static IsString(val: any): boolean
    {
        return typeof val == 'string' || val instanceof String;
    }

    /**
     * Deserialize json object object into TypeScript one.
     * @param json json object that must have its class name in '$' field
     * @param environments list of modules containing all types that can be encountered during deserialization
     * @return deserialized typescript object of specified type  
     */
    public static Ressurect(val: any, environments: any[]): any 
    {
        if(val instanceof Array)
        {
            if(val.length == 0)
            {
                return val;
            }
            else 
            {
                let firstElement = val[0];
                if(typeof firstElement !== 'object')
                {
                    return val;
                }
                else
                {
                    let arr = [];
                    for (var i = 0; i < val.length; i++) 
                    {
                        var element = val[i];
                        arr.push(Helper.RessurectValue(element, environments));
                    }

                    return arr;
                }   
            }
        }
        else
        {
            return Helper.RessurectValue(val, environments);
        }
    }
}

Some notes for the above. It works based on the assumption that:

  1. There is a parameterless constructor in every type that can be deserialized.
  2. Every JSON object contain field '$' with its class name inside.

Sample of usage. Lets suppose yu have defined all your serializable classes in one external mode called 'Types'. Then to deserialize JSON object you write:

import * as types from './Types'

//Some code to get jsonObj that has jsonObj.$ set to "RealObject" - a type from "Types" module

let realObj = <types.RealObject>Helper.Resurrect(jsonObj, [types]);

Hope this helps.

Community
  • 1
  • 1
Amid
  • 21,508
  • 5
  • 57
  • 54
1

The problem is what you receive are objects which are complying to the interface, but they don't have a method property inside.

What you need to do in order to get this working is, you need to create new objects from type IClass in the reponse handler which extend the objects inside data:

$.get("/some/url", (data: Array<IInterface>) => {
    return data.map((d) => return new IClass(d.id));
});
FlorianTopf
  • 938
  • 6
  • 10