<

1. Reflect #

1.1 Reflect #

1.2 Reflect Metadata #

1.2.1 defineMetadata #

// define metadata on an object or property
Reflect.defineMetadata(metadataKey, metadataValue, target);
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey);

// get metadata value of a metadata key on the prototype chain of an object or property
let result = Reflect.getMetadata(metadataKey, target);
let result = Reflect.getMetadata(metadataKey, target, propertyKey);
let target = {};
Reflect.defineMetadata("name", "zhufeng", target);
Reflect.defineMetadata("name", "world", target, 'hello');
console.log(Reflect.getOwnMetadata("name", target));
console.log(Reflect.getOwnMetadata("name", target, "hello"));

1.2.2 decorator #

// apply metadata via a decorator to a constructor
@Reflect.metadata(metadataKey, metadataValue)
class C {
  // apply metadata via a decorator to a method (property)
  @Reflect.metadata(metadataKey, metadataValue)
  method() {}
}
import 'reflect-metadata';
let target = {};
Reflect.defineMetadata('name','zhufeng',target);
Reflect.defineMetadata('name', 'world', target,'hello');
console.log(Reflect.getOwnMetadata('name',target));
console.log(Reflect.getOwnMetadata('name', target, 'hello'));
console.dir(target);

function classMetadata(key,value){
     return function(target){
         Reflect.defineMetadata(key, value, target);
     }
}
function methodMetadata(key, value) {
    //target类的原型
    return function (target,propertyName) {
        //Person.prototype.hello.name=world
        Reflect.defineMetadata(key, value, target, propertyName);
    }
}
//decorator
//给类本身增加元数据
//@Reflect.metadata('name','Person')
@classMetadata('name', 'Person')
class Person{
    //给类的原型增加元数据
  //@Reflect.metadata('name', 'world')
   @methodMetadata('name', 'world')
   hello():string{ return 'world'}
}
console.log(Reflect.getMetadata('name', Person));
console.log(Reflect.getMetadata('name', new Person(),'hello'));

1.2 tsconfig.json #

1.2.1 tsconfig.json #

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true
  },
  "exclude": [
    "node_modules",
    "dist"
  ]
}

1.2.2 tsconfig.build.json #

{
    "extends": "./tsconfig.json",
    "exclude": [
        "node_modules",
        "test",
        "dist",
        "**/*spec.ts"
    ]
}

2. IOC和DI #

2.1 创建电脑 #

export interface Monitor{}
class Monitor27inch implements Monitor{}
interface Host{}
class LegendHost implements Host { }
class Computer{
    monitor:Monitor;
    host:Host;
    constructor(){
        this.monitor = new Monitor27inch();
        this.host = new LegendHost();
    }
    startup(){
        console.log('组装好了,可以开机了');
    }
}
let computer = new Computer();
computer.startup();

2.2 可以传递零件 #

interface Monitor{}
class Monitor27inch implements Monitor{}
interface Host{}
class LegendHost implements Host { }
export class Computer{
    monitor:Monitor;
    host:Host;
    constructor(monitor, host){
        this.monitor = monitor;
        this.host = host;
    }
    startup(){
        console.log('组装好了,可以开机了');
    }
}
+let monitor = new Monitor27inch();
+let host = new LegendHost();
+let computer = new Computer(monitor, host);
computer.startup();

2.3 Ioc和DI #

2.3.1 IoC(Inversion of Control) #

2.3.2 DI(Dependency Injection) #

ioctdi

3. Nest.js #

3.1 安装依赖 #

cnpm i @nestjs/core @nestjs/common @nestjs/platform-express rxjs reflect-metadata -D

3.2 实现应用 #

3.2.1 main.ts #

src\main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

3.2.2 app.module.ts #

src\app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from "./app.controller";
@Module({
  controllers: [AppController]
})
export class AppModule {}

3.2.3 app.controller.ts #

src\app.controller.ts

import { Get, Controller } from "@nestjs/common";

@Controller('/')
export class AppController {
  @Get('/hello')
  hello() {
    return 'hello';
  }
}

3.2.4 package.json #

  "scripts": {
    "start:dev": "nest start --watch"
  },

3.3 依赖注入 #

3.3.1 src\main.ts #

import { Module } from '@nestjs/common';
import { AppController } from "./app.controller";
+import { AppService } from "./app.service";
+import { UseClassLoggerService, UseValueLoggerService, UseValueLoggerServiceStringToken, UseFactoryLoggerService } from "./logger.service";

@Module({
  controllers: [AppController],
+  providers: [
    AppService,
+    {
+      provide: UseClassLoggerService,
+      useClass: UseClassLoggerService
+    },
+    {
+      provide: UseValueLoggerService,
+      useValue: new UseValueLoggerService()
+    },
+    {
+      provide: 'StringToken',
+      useValue: new UseValueLoggerServiceStringToken()
+    },
+    {
+      provide: 'FactoryToken',
+      useFactory: () => new UseFactoryLoggerService()
+    }
+  ]
+})
export class AppModule {}

3.3.2 app.controller.ts #

src\app.controller.ts

import { Get, Controller,Inject } from "@nestjs/common";
import { AppService } from "./app.service";
+import { UseClassLoggerService, UseValueLoggerService, UseFactoryLoggerService, UseValueLoggerServiceStringToken } from "./logger.service";
@Controller('/')
export class AppController {
  constructor(
+    private readonly appService: AppService,
+    private readonly useClassLoggerService: UseClassLoggerService,
+    private readonly useValueLoggerService: UseValueLoggerService,
+    @Inject("StringToken") private readonly useValueLoggerServiceStringToken: UseValueLoggerServiceStringToken,
+    @Inject("FactoryToken")  private readonly useFactoryLoggerService: UseFactoryLoggerService
+    ) { }
  @Get('/hello')
  hello() {
+    this.useClassLoggerService.log('useClassLoggerService');
+    this.useValueLoggerService.log('useValueLoggerService');
+    this.useValueLoggerServiceStringToken.log('StringToken');
+    this.useFactoryLoggerService.log('FactoryToken');
    return this.appService.getHello();
  }
}

3.3.3 src\app.service.ts #

src\app.service.ts

import { Injectable } from "@nestjs/common";
+import { UseClassLoggerService } from "./logger.service";
@Injectable()
export class AppService {
+  constructor(private readonly useClassLoggerService: UseClassLoggerService,){}
  getHello(): string {
+    this.useClassLoggerService.log('getHello');
    return "Hello";
  }
}

3.3.4 logger.service.ts #

src\app.logger.ts

import { Injectable } from "@nestjs/common";

@Injectable()
export class UseClassLoggerService {
  constructor(){
    console.log('创建 UseClassLoggerService');
  }
  log(message:string) {
   console.log(message);
  }
}


export class UseValueLoggerService {
  log(message: string) {
    console.log(message);
  } 
}
export class UseValueLoggerServiceStringToken {
  log(message: string) {
    console.log(message);
  }
}
export class UseFactoryLoggerService {
  log(message: string) {
    console.log(message);
  }
}

inversejs

4. IOC #

4.1 注册Provider #

4.1 type.ts #

export interface Type<T>{
    new(...args: any[]): T;
}
//class Person{}
//let t:Type<Person> = Person;s

4.2 Container.ts #

import {Provider,Token} from "./provider";
export class Container {
    public providers = new Map<Token<any>, Provider<any>>();
    addProvider<T>(provider: Provider<T>) {
        this.providers.set(provider.provide, provider);
    }
}

4.3 provider.ts #

provider.ts

import { Type } from "./type";
export class InjectionToken {
    constructor(public injectionIdentifier: string) { }
}
//Token 类型是一个联合类型,既可以是一个函数类型也可以是 InjectionToken 类型
export type Token<T> = Type<T> | InjectionToken;

export interface BaseProvider<T> {
    provide: Token<T>;
}

export interface ClassProvider<T> extends BaseProvider<T> {
    provide: Token<T>;
    useClass: Type<T>;
}

export interface ValueProvider<T> extends BaseProvider<T> {
    provide: Token<T>;
    useValue: T;
}

export interface FactoryProvider<T> extends BaseProvider<T> {
    provide: Token<T>;
    useFactory: () => T;
}

export type Provider<T> =
    | ClassProvider<T>
    | ValueProvider<T>
    | FactoryProvider<T>;    

4.4 index.ts #

export * from "./container";

4.5 ioc\index.spec.ts #

import {Container} from './';
let container = new Container();
const point = { x: 100,y:100 };
class BasicClass { }
// 注册ClassProvider
container.addProvider({ provide: BasicClass, useClass: BasicClass });
// 注册ValueProvider
container.addProvider({ provide: BasicClass, useValue: point });
// 注册FactoryProvider
container.addProvider({ provide: BasicClass, useFactory: () => point });
console.log(container.providers);

4.2 装饰器 #

4.2.1 Injectable #

declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction;

4.2.2 Inject #

declare type ParameterDecorator = (target: Object, 
  propertyKey: string | symbol, parameterIndex: number ) => void

4.2.3 实现 #

4.2.3.1 Injectable.ts #
import "reflect-metadata";
const INJECTABLE_METADATA_KEY = Symbol("INJECTABLE_KEY");

export function Injectable() {
    return function (target: any) {
        Reflect.defineMetadata(INJECTABLE_METADATA_KEY, true, target);
        return target;
    };
}
4.2.3.2 Inject.ts #
import 'reflect-metadata';
import { Token } from './provider';

const INJECT_METADATA_KEY = Symbol('INJECT_KEY');

export function Inject(token: Token<any>) {
    return function (target: any, _: string | symbol, index: number) {
        Reflect.defineMetadata(INJECT_METADATA_KEY, token, target, `index-${index}`);
        return target;
    };
}

4.3 实现 inject #

4.3.1 Container.ts #

import {
    Provider, Token, InjectionToken,
    ClassProvider, ValueProvider,FactoryProvider,
    isClassProvider, isValueProvider,isFactoryProvider} from "./provider";
import { Type } from "./type";    
export class Container {
    public providers = new Map<Token<any>, Provider<any>>();
    addProvider<T>(provider: Provider<T>) {
        this.providers.set(provider.provide, provider);
    }
    inject<T>(type: Token<T>): T {
        let provider = this.providers.get(type);
        return this.injectWithProvider(type, provider);
    }
    private injectWithProvider<T>(type: Token<T>, provider?: Provider<T>): T {
        if (isClassProvider(provider)) {
            //TODO
        } else if (isValueProvider(provider)) {
            return this.injectValue(provider as ValueProvider<T>);
        } else if (isFactoryProvider(provider)){
            return this.injectFactory(provider as FactoryProvider<T>);
        }
    }
    private injectValue<T>(valueProvider: ValueProvider<T>): T {
        return valueProvider.useValue;
    }
    private injectFactory<T>(valueProvider: FactoryProvider<T>): T {
        return valueProvider.useFactory();
    }
}

4.4 __metadata #

decoratorflow

tsc params/index.ts --experimentalDecorators --emitDecoratorMetadata --target es5

4.4.1 装饰器 #

/**
 * 类装饰器
 * @param constructor 类的构造函数
 */
function classDecorator(constructor: Function) {}
/**
 * 属性装饰器
 * @param target 静态成员来说是类的构造函数,对于实例成员是类的原型对象
 * @param property  属性的名称
 */
function propertyDecorator(target: any, property: string) {}
/**
 * 方法装饰器
 * @param target 静态成员来说是类的构造函数,对于实例成员是类的原型对象
 * @param property 方法的名称
 * @param descriptor 方法描述符
 */
function methodDecorator(target: any, property: string, descriptor: PropertyDescriptor) { }
/**
 * 参数装饰器
 * @param target 静态成员是类的构造函数,实例成员是类的原型对象
 * @param methodName 方法名
 * @param paramsIndex 参数在函数列表中的索引
 */
function paramDecorator(target: any, methodName: string, paramsIndex: number) { }

4.4.2 params\index.ts #

import 'reflect-metadata';
interface Type<T> {
    new(...args: any[]): T;
}
class InjectionToken {
    constructor(public injectionIdentifier: string) { }
}
type Token<T> = Type<T> | InjectionToken;

const INJECT_METADATA_KEY = 'INJECT_KEY';

function Inject(token: Token<any>) {
    return function (target: any, _propertyName: string | symbol, index: number) {
        Reflect.defineMetadata(INJECT_METADATA_KEY, token, target, `index-${index}`);
        return target;
    };
}

class Car { }

class LoggerService { }

class Wife {
    constructor(
        private car: Car,
        @Inject(new InjectionToken('Logger')) private loggerService:LoggerService
    ) { }
}
console.log(Wife);

4.4.2 params\index.js #

"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
require("reflect-metadata");
var InjectionToken = /** @class */ (function () {
    function InjectionToken(injectionIdentifier) {
        this.injectionIdentifier = injectionIdentifier;
    }
    return InjectionToken;
}());
var INJECT_METADATA_KEY = 'INJECT_KEY';
function Inject(token) {
    return function (target, _propertyName, index) {
        Reflect.defineMetadata(INJECT_METADATA_KEY, token, target, "index-" + index);
        return target;
    };
}
var Car = /** @class */ (function () {
    function Car() {
    }
    return Car;
}());
var LoggerService = /** @class */ (function () {
    function LoggerService() {
    }
    return LoggerService;
}());
var Wife = /** @class */ (function () {
    function Wife(car, loggerService) {
        this.car = car;
        this.loggerService = loggerService;
    }
    Wife = __decorate([
        __param(1, Inject(new InjectionToken('Logger'))),
        __metadata("design:paramtypes", [Car,
            LoggerService])
    ], Wife);
    return Wife;
}());
console.log(Wife);

4.4.3 params\imp.js #

"use strict";
require("reflect-metadata");
/**
 * 装饰器执行器
 * @param {*} decorators 装饰器数组
 * @param {*} target 装饰器目标
 * @param {*} key 元数据键
 * @param {*} desc 方法的描述符
 */
var __decorate = function (decorators, target, key, desc) {
    /*获取参数长度,当参数长度小于3,说明目标就是target,否则目标为方法描述符
        描述符不存在时,通过key从target获取,即认为key是方法名 */
    var argsLength = arguments.length,
        decoratorTarget =
            argsLength < 3
                ? target
                : desc === null
                    ? (desc = Object.getOwnPropertyDescriptor(target, key))
                    : desc;
    /* 如果Reflect的decorate方法存在,则调用这个方法为目标调用装饰器方法数组,这个方法在reflect-metadata包中实现 */
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
      decoratorTarget = Reflect.decorate(decorators, target, key, desc);
    /*
    如果Reflect.decorate方法不存在,则手动调用装饰方法,注意是倒序调用
    如果参数长度小于3说明是类装饰器,直接将类传递给装饰器方法
    如果参数长度等于3说明是类装饰器,但是key参数存在,与类一同传递给装饰器方法
    如果参数长度大于3说明是方法装饰器,将类、key、方法描述符传递给装饰器方法
    同时获取装饰器方法执行完毕的target给decoratorTarget,如果装饰器方法执行完毕没有返回值,则使用之前的decoratorTarget */ 
    else {
      for (var i = decorators.length - 1; i >= 0; i--) {
        let decorator = decorators[i];
        if (decorator) {
          decoratorTarget =
            (argsLength < 3
              ? decorator(decoratorTarget)
              : argsLength > 3
              ? decorator(target, key, decoratorTarget)
              : decorator(target, key)) || decoratorTarget;
        }
      }
    }
    /* 返回decoratorTarget,参数小于3时为类对象,参数大于3时为方法描述符,当为描述符时需要重新将其定义到target上 */
    return (
      argsLength > 3 &&
        decoratorTarget &&
        Object.defineProperty(target, key, decoratorTarget),
      decoratorTarget
    );
}

/**
 *  装饰器工厂,可以获取在类上定义指定键值对的装饰器,一般用来定义emitDecoratorMetadata
 * @param {*} k 属性名
 * @param {*} v 属性值
 */
var __metadata = function (k, v) {
    return Reflect.metadata(k, v);
};

/**
 * 参数装饰器工厂,用来获取参数装饰器
 * @param {*} paramIndex  参数下标
 * @param {*} decorator 装饰器
 * @param {*} target 装饰的目标为类
 * @param {*} key 属性名
*/
var __param = function (paramIndex, decorator) {
    return function (target, key) {
        decorator(target, key, paramIndex);
    };
};

var InjectionToken = (function () {
    function InjectionToken(injectionIdentifier) {
        this.injectionIdentifier = injectionIdentifier;
    }
    return InjectionToken;
})();
var INJECT_METADATA_KEY = "INJECT_KEY";
/**
 * 返回 token装饰器
 * @param {*} token 标识符 
 * @param {*} target 装饰的目标
 * @param {*} _propertyName 属性名
 * @param {*} index 索引
 */
function Inject(token) {
    return function (target, _propertyName, index) {
        Reflect.defineMetadata(INJECT_METADATA_KEY,token,target,"index-" + index);
        return target;
    };
}
var Car = (function () {
    function Car() { }
    return Car;
})();
var LoggerService = (function () {
    function LoggerService() { }
    return LoggerService;
})();
var Wife = (function () {
    function Wife(car, loggerService) {
        this.car = car;
        this.loggerService = loggerService;
    }
    Wife = __decorate(
        [
            __param(1, Inject(new InjectionToken("Logger"))),
            __metadata("design:paramtypes", [Car, LoggerService]),
        ],
        Wife
    );
    return Wife;
})();
console.log(Reflect.getMetadata(INJECT_METADATA_KEY, Wife, "index-1"));
console.log(Wife);

4.5 实现注入 #

4.5.1 Container.ts #

import {
    Provider, Token, InjectionToken,
    ClassProvider, ValueProvider,FactoryProvider,
+    isClassProvider, isValueProvider,isFactoryProvider} from "./provider";
+import { getInjectionToken, } from "./inject";    
+import { Type } from "./type";    
+type InjectableParam = Type<any>;
+const REFLECT_PARAMS = "design:paramtypes";    
export class Container {
    public providers = new Map<Token<any>, Provider<any>>();
    addProvider<T>(provider: Provider<T>) {
        this.providers.set(provider.provide, provider);
    }
+    inject<T>(type: Token<T>): T {
+        let provider = this.providers.get(type);
+        return this.injectWithProvider(type, provider);
+    }
+    private injectWithProvider<T>(type: Token<T>, provider?: Provider<T>): T {
+        if (provider === undefined) {
+            throw new Error(`No provider for type ${this.getTokenName(type)}`);
+        }
+        if (isClassProvider(provider)) {
+            return this.injectClass(provider as ClassProvider<T>);
+        } else if (isValueProvider(provider)) {
+            return this.injectValue(provider as ValueProvider<T>);
+        } else if (isFactoryProvider(provider)){
+            return this.injectFactory(provider as FactoryProvider<T>);
+        }
+    }
+    private injectValue<T>(valueProvider: ValueProvider<T>): T {
+        return valueProvider.useValue;
+    }
+    private injectFactory<T>(valueProvider: FactoryProvider<T>): T {
+        return valueProvider.useFactory();
+    }
+    private injectClass<T>(classProvider: ClassProvider<T>): T {
+        const target = classProvider.useClass;
+        const params = this.getInjectedParams(target);
+        return Reflect.construct(target, params);
+    }
+    private getInjectedParams<T>(target: Type<T>) {
+        const argTypes = Reflect.getMetadata(REFLECT_PARAMS, target) as (
+            | InjectableParam
+            | undefined)[];
+        if (argTypes === undefined) {
+            return [];
+        }
+        return argTypes.map((argType, index) => {
+            const overrideToken = getInjectionToken(target, index);
+            const actualToken = overrideToken === undefined ? argType : overrideToken;
+            let provider = this.providers.get(actualToken);
+            return this.injectWithProvider(actualToken, provider);
+        });
+    }
+    private getTokenName<T>(token: Token<T>) {
+        return token instanceof InjectionToken
+            ? token.injectionIdentifier
+            : token.name;
+    }
}

4.5.2 ioc\provider.ts #

import { Type } from "./type";
export class InjectionToken {
    constructor(public injectionIdentifier: string) { }
}
//Token 类型是一个联合类型,既可以是一个函数类型也可以是 InjectionToken 类型
export type Token<T> = Type<T> | InjectionToken;

export interface BaseProvider<T> {
    provide: Token<T>;
}

export interface ClassProvider<T> extends BaseProvider<T> {
    provide: Token<T>;
    useClass: Type<T>;
}

export interface ValueProvider<T> extends BaseProvider<T> {
    provide: Token<T>;
    useValue: T;
}

export interface FactoryProvider<T> extends BaseProvider<T> {
    provide: Token<T>;
    useFactory: () => T;
}

export type Provider<T> =
    | ClassProvider<T>
    | ValueProvider<T>
    | FactoryProvider<T>;

+export function isClassProvider<T>(
+    provider: BaseProvider<T>
+): provider is ClassProvider<T> {
+    return (provider as any).useClass !== undefined;
+}

+export function isValueProvider<T>(
+    provider: BaseProvider<T>
+): provider is ValueProvider<T> {
+    return (provider as any).useValue !== undefined;
+}

+export function isFactoryProvider<T>(
+    provider: BaseProvider<T>
+): provider is FactoryProvider<T> {
+    return (provider as any).useFactory !== undefined;
+}    

4.5.3 Inject.ts #

ioc\Inject.ts

import 'reflect-metadata';
import { Token } from './provider';

const INJECT_METADATA_KEY = Symbol('INJECT_KEY');

export function Inject(token: Token<any>) {
    return function (target: any, _: string | symbol, index: number) {
        Reflect.defineMetadata(INJECT_METADATA_KEY, token, target, `index-${index}`);
        return target;
    };
}
export function getInjectionToken(target: any, index: number) {
    return Reflect.getMetadata(INJECT_METADATA_KEY, target, `index-${index}`) as
        | Token<any>
        | undefined;
}

4.5.4 Injectable.ts #

ioc\Injectable.ts

import "reflect-metadata";
const INJECTABLE_METADATA_KEY = Symbol("INJECTABLE_KEY");

export function Injectable() {
    return function (target: any) {
        Reflect.defineMetadata(INJECTABLE_METADATA_KEY, true, target);
        return target;
    };
}

4.6 index.spec.ts #

import { Container } from "./container";
import { Injectable } from "./injectable";
import { Inject } from "./inject";
import { InjectionToken } from "./provider";

const HouseStringToken = new InjectionToken("House");

@Injectable()
class Car { }

@Injectable()
class House { }

@Injectable()
class Wife {
    constructor(
        private car: Car,
        @Inject(HouseStringToken) private house: House
    ) { }
}

const container = new Container();

container.addProvider({
    provide: HouseStringToken,
    useValue: new House(),
});

container.addProvider({ provide: Car, useClass: Car });
container.addProvider({ provide: Wife, useClass: Wife });
const wife = container.inject(Wife);
console.dir(wife);

5. debugger #

cnpm i ts-node typescript reflect-metadata -D

.vscode\launch.json

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug ts",
            "type": "node",
            "request": "launch",
            "runtimeArgs": [
                "-r",
                "ts-node/register"
            ], //核心
            "args": [
                "${relativeFile}"
            ]
        }
    ]
}