简体   繁体   中英

How to assign prototype to generic type

I'm trying to build a sessionStorage service that can parse objects strings to a generic type. but with this implementation it only returns an object without prototype (no object functions).

here is the code:

public static getObject<T>(key: string): T{

    return JSON.parse(sessionStorage.getItem(key)) as T;
}

Regards!

JSON does not encode functions, or prototypes, or constructors. Parsed JSON will never create an instance of any class for you. It's just raw structured data. And this is true for most ways that you might serialize javascript data.

There is no good way around that. It's up to you to structure your codebase so that things will work with this in mind.

There are an innumerable number of solutions to this, but for example you could pass this data to a constructor after you fetch it.

Something like this:

const data = sessionStorageInstance.getObject<MyInterfaceHere>('some/key')
const instance = new MyClassHere(data)

I assume you're using your code somewhat like this?

class SessionStorageManager {
    public static getObject<T>(key: string): T{
        return JSON.parse(sessionStorage.getItem(key)) as T;
    }
}

const test = SessionStorageManager.getObject('test');

In this example, T has no default value set where getObject is defined, and I haven't set a type when calling getObject either, so the type of test according to TypeScript is unknown .

If you want to make sure that anything returned by getObject is a type of Object , then you'll want to use extends when creating your generic function to explain this to TypeScript. For example:

public static getObject<T extends object>(key: string): T{
    return JSON.parse(sessionStorage.getItem(key)) as T;
}

However, be wary that the use of as here is essentially bypassing the type check. Only do this if you are confident that you will always be getting an object out of JSON.parse . Remember that you can still get primitives out of it, in cases such as JSON.parse('2') or JSON.parse("string") , and if the JSON string is malformed then JSON.parse will throw an error.

If you want to be type safe, then you will need to add a type check to support that as type assertion . Depending on how you are using this code, you might need to do that outside the getObject function since you might now know what custom type guard may need to be used to determine whether or not an object really is of the given type of T .

To ensure these type guards must be used, you may want to instead have getObject return the type unknown , which means TypeScript won't let you make any assumptions about the type until you've done some type narrowing. For example:

class SessionStorageManager {
    public static getObject(key: string): unknown {
        return JSON.parse(sessionStorage.getItem(key));
    }
}

const obj = SessionStorageManager.getObject('test'); // type unknown

if (typeof obj === 'object') {
    // type object
}

Unfortunately, generic type only happens in TypeScript as some kind of model design assistance. It will never be compiled into JavaScript file. Take your sessionStorage service as an example:

// SessionStorageService.ts
class SessionStorageService {
  public static getObject<T>(key: string): T{
    return JSON.parse(sessionStorage.getItem(key)) as T;
  }
}

If we compile above TypeScript file into JavaScript using tsc , we can find T is completely removed:

// SessionStorageService.js
var SessionStorageService = /** @class */ (function () {
    function SessionStorageService() {
    }
    SessionStorageService.getObject = function (key) {
        return JSON.parse(sessionStorage.getItem(key));
    };
    return SessionStorageService;
}());

To return object with class/prototype in deserialization, you need to save class/prototype information during serialization. Otherwise, these class/prototype information will be lost.

I've made an npm module named esserializer to solve this problem automatically: save JavaScript class instance values during serialization, in plain JSON format, together with its class name information. Later on, during the deserialization stage (possibly in another process or on another machine), esserializer can recursively deserialize object instance, with all Class/Property/Method information retained, using the same class definition. For your SessionStorageService, it would look like:

// SessionStorageService.ts
const ESSerializer = require('esserializer');
ESSerializer.registerClasses([ClassA, ClassB]); //register all classes possibly involved in serialization.

class SessionStorageService {
  public static getObject(key: string){
    return ESSerializer.deserialize(sessionStorage.getItem(key));
  }
}

// Previously, set sessionStorage in another file
const ESSerializer = require('esserializer');
sessionStorage.setItem(key, ESSerializer.serialize(anObj));

You could do it using Object.create to create an empty object with the correct prototype, and Object.assign to add all of the properties from the JSON data.

Note that you need to pass cls as an argument when you call this function, since otherwise there is no way of knowing which class you want to set as the prototype. If the calling code doesn't necessarily know which class the object should be, you could come up with some way to encode this in your key (eg instead of someKey you could make it YourClass-someKey and then have a registry of classes so you could look up YourClass by name).

type ClassOf<T> = new(...args: any[]) => T

function makeObject<T>(cls: ClassOf<T>, json: string): T {
    const obj: T = Object.create(cls.prototype);
    Object.assign(obj, JSON.parse(json));
    return obj;
}

Example usage:

class Kitten {
    constructor(public name: string) {}
    meow(): void { console.log(this.name + ' says meow!'); }
}

// const mittens: Kitten
const mittens = makeObject(Kitten, '{"name": "Mittens"}');
// Mittens says meow!
mittens.meow();

Playground Link

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM