Types serialization & The metadata reflection API
- In this post we will learn about:
- Why we need reflection in JavaScript?
- The metadata reflection API
- Basic type serialization
- Complex type serialization
- Let’s start by learning why we need reflection in JavaScript.
- 本片相当于短篇的引入,更多实践内容需要自行发掘,切该作者后期4年期间也未曾再更新相关内容。
Why we need reflection in JavaScript?
The name reflection is used to describe code which is able to inspect other code in the same system (or itself).
Reflection is useful for a number of use cases (Composition/Dependency Injection, Run-time Type Assertions, Testing).
Our JavaScript applications are getting bigger and bigger and we are starting to need some tools (like inversion of control containers) and features like (run-time type assertions) to manage this increasing complexity. The problem is that because there is not reflection in JavaScript some of this tools and features cannot be implemented, or at least they can not be implemented to be as powerful as they are in programming languages like C# or Java.
A powerful reflection API should allow us to examine an unknown object at run-time and find out everything about it. We should be able to find things like:
- The name of the entity.
- The type of the entity.
- Which interfaces are implemented by the entity.
- The name and types of the properties of the entity.
- The name and types of the constructor arguments of the entity.
In JavaScript we can use functions like Object.getOwnPropertyDescriptor() or Object.keys() to find some information about an entity but we need reflection to implement more powerful development tools.
However, things are about to change because TypeScript is starting to support some Reflection features. Let’s take a look to this features:
The metadata reflection API
The native JavaScript support for a metadata reflection API is in an early stage of development. There is a proposal to add Decorators to ES7, along with a prototype for an ES7 Reflection API for Decorator Metadata, which is available online.
Some of the guys from the TypeScript team have been working on a Polyfill for the prototype of the ES7 Reflection API and the TypeScript compiler is now able to emit some serialized design-time type metadata for decorators.
We can use this metadata reflection API polyfill by using the reflect-metadata package:
|
We must use it with TypeScript 1.5 and the compiler flag
emitDecoratorMetadata
set to true. We also need to including a reference toreflect-metadata.d.ts.
and load theReflect.js
file.We then need to implement our own decorators and use one of the available reflect metadata design keys. For the moment there are only three available:
- Type metadata uses the metadata key
"design:type"
. - Parameter type metadata uses the metadata key
"design:paramtypes"
. - Return type metadata uses the metadata key
"design:returntype"
.
- Type metadata uses the metadata key
Let’s see a couple of examples.
Obtaining type metadata using the reflect metadata API
- Let’s declare the following property decorator:
|
- We can then apply it to one of the properties of a class to obtain its type:
|
The example above logs the following in console:
attr1 type: String
Obtaining Parameter type metadata using the reflect metadata API
- Let’s declare the following parameter decorator:
|
- We can then apply it to one of the method of a class to obtain information about the types of its arguments:
|
The example above logs the following in console:
doSomething param types: String, Number, Foo, Object, Object, Function, Function
Obtaining return type metadata using the reflect metadata API
We can also get information about the return type of a method using the "design:returntype"
metadata key:
|
Basic type serialization
Let’s take a look to the
"design:paramtypes"
example above again. Notice the that interfaces IFoo and object literal{ test : string}
are serialized asObject
. This is because TypeScript only supports basic type serialization. The basic type serialization rules are:number
serialized asNumber
string
serialized asString
boolean
serialized asBoolean
any
serialized asObject
void
serializes asundefined
Array
serialized asArray
If a
Tuple
, serialized asArray
If a
class
serialized it as the classconstructor
If an
Enum
serialized it asNumber
If has at least one call signature, serialized as
Function
Otherwise serialized as
Object
(Includinginterfaces)
Interfaces and object literals may be serialize in the future via complex type serialization but this feature is not available at this time.
Complex types serialization
The TypeScript team is working on a proposal that will allow us to generate metadata for complex types.
They proposal describes how some complex types will be serialized. The serialization rules above will still be used for basic type but a different serialization logic will be used for complex types. In the proposal there is a base type that is used to describe all the possible types:
|
- We can also find the classes that will be used to describe each of the possible types. For example, we can find the class proposed to be used to serialize genetic
interfaces interface foo
{ /* ... */}:
|
- As we can see above there will be an attribute which indicates the implemented interfaces:
|
That information could be used to do things like validate if an entity implements certain interface at run-time, which could be really useful for an IoC container.
We don’t know when complex type serialization support will be added to TypeScript but we cannot wait because we have plans to use it to add some cool features to our awesome IoC container for JavaScript: InversifyJS.
Conclusion
In this series we have learned in-depth 4 out of the 4 available types of decorators, how to create a decorator factory and how to use a decorator factory to implement configurable decorators.
We also know how to work with the metadata reflection API.