web-dev-qa-db-ja.com

JSONオブジェクトをTypeScriptクラスにキャストする方法

リモートのRESTサーバーからJSONオブジェクトを読みました。このJSONオブジェクトは、TypeScriptクラスのすべてのプロパティを(設計上)持っています。受け取ったJSONオブジェクトをvar型にキャストするにはどうすればいいですか?

TypeScriptの変数に値を入れたくありません(つまり、このJSONオブジェクトを受け取るコンストラクターがあります)。サイズが大きく、サブオブジェクト間やサブオブジェクト間、プロパティごとにすべてをコピーするのは非常に時間がかかります。

更新: ただし、 TypeScriptインタフェースにキャストすることもできます

302
David Thielen

Ajaxリクエストからの単純で古いJavaScriptの結果をプロトタイプのJavaScript/TypeScriptクラスインスタンスに単純にキャストすることはできません。それをするための多くのテクニックがあり、そして一般的にデータをコピーすることを含みます。クラスのインスタンスを作成しない限り、メソッドやプロパティはありません。単純なJavaScriptオブジェクトのままになります。

データを扱うだけの場合は(純粋にコンパイル時の構造体であるため)インターフェイスにキャストするだけで済みますが、これにはデータインスタンスを使用し、そのデータを使って操作を実行するTypeScriptクラスを使用する必要があります。

データをコピーするいくつかの例:

  1. AJAX JSONオブジェクトを既存のオブジェクトにコピーする
  2. JSON文字列をJavaScriptの特定のオブジェクトプロトタイプに解析する

本質的には、あなただけです:

var d = new MyRichObject();
d.copyInto(jsonResult);
125
WiredPrairie

私は同じ問題を抱えていました、そして私は仕事をするライブラリを見つけました: https://github.com/pleerock/class-transformer

それはこのように動作します :

        let jsonObject = response.json() as Object;
        let fooInstance = plainToClass(Models.Foo, jsonObject);
        return fooInstance;

それは入れ子になった子をサポートしますが、あなたはあなたのクラスのメンバーを飾る必要があります。

69
Pak

TypeScriptでは、 型アサーション をインタフェースと総称を使用して実行できます。

var json = Utilities.JSONLoader.loadFromFile("../docs/location_map.json");
var locations: Array<ILocationMap> = JSON.parse(json).location;

ILocationMapはデータの形状を表します。この方法の利点は、JSONにもっと多くのプロパティを含めることができますが、その形状はインタフェースの条件を満たすことです。

私はそれが役立つことを願っています!

41
user756310

ES6を使用している場合は、これを試してください。

class Client{
  name: string

  displayName(){
    console.log(this.name)
  }
}

service.getClientFromAPI().then(clientData => {

  // Here the client data from API only have the "name" field
  // If we want to use the Client class methods on this data object we need to:
  let clientWithType = Object.assign(new Client(), clientData)

  clientWithType.displayName()
})

しかし、悲しいことに、この方法は入れ子オブジェクトには機能しません。

26
ilovezcd

TypeScriptクラスへのJSONの一般的なキャストに関する非常に興味深い記事を見つけました。

http://cloudmark.github.io/Json-Mapping/ /

次のようなコードになります。

let example = {
                "name": "Mark", 
                "surname": "Galea", 
                "age": 30, 
                "address": {
                  "first-line": "Some where", 
                  "second-line": "Over Here",
                  "city": "In This City"
                }
              };

MapUtils.deserialize(Person, example);  // custom class
25
Philip Miglinci

JsonがTypeScriptクラスと同じプロパティを持つと仮定すると、JsonのプロパティをTypeScriptオブジェクトにコピーする必要はありません。コンストラクタにjsonデータを渡してTypeScriptオブジェクトを構築するだけです。

あなたのAjaxコールバックで、あなたは会社を受け取ります:

onReceiveCompany( jsonCompany : any ) 
{
   let newCompany = new Company( jsonCompany );

   // call the methods on your newCompany object ...
}

それを機能させるために:

1)パラメータとしてjsonデータを受け取るコンストラクタをTypeScriptクラスに追加します。そのコンストラクターでは、次のようにjsonオブジェクトをjQueryで拡張します。$.extend( this, jsonData)。 $ .extendはjsonオブジェクトのプロパティを追加しながらjavascriptプロトタイプを保持することを可能にします。

2)リンクオブジェクトについても同じことをする必要があることに注意してください。例のEmployeesの場合は、jsonデータの一部を従業員用に取るコンストラクタも作成します。 jsonの従業員をTypeScriptのEmployeeオブジェクトに変換するには、$ .mapを呼び出します。

export class Company
{
    Employees : Employee[];

    constructor( jsonData: any )
    {
        $.extend( this, jsonData);

        if ( jsonData.Employees )
            this.Employees = $.map( jsonData.Employees , (emp) => {
                return new Employee ( emp );  });
    }
}

export class Employee
{
    name: string;
    salary: number;

    constructor( jsonData: any )
    {
        $.extend( this, jsonData);
    }
}

これはTypeScriptクラスとjsonオブジェクトを扱うときに私が見つけた最良の解決策です。

16

TLDR:ワンライナー

// This assumes your constructor method will assign properties from the arg.
.map((instanceData: MyClass) => new MyClass(instanceData));

詳細な回答

クラス内で宣言されていない(定義されたクロージャと同様に)不適切なプロパティでクラスインスタンスを不適切に散らす可能性があるため、私はnot)を推奨します。

あなたが逆シリアル化しようとしているクラスでは、私はあなたが逆シリアル化したいすべてのプロパティが定義されていることを確認します(null、空の配列など)。初期値を使用してプロパティを定義することで、クラスメンバを繰り返して値を代入しようとしたときの可視性を公開します(後述のdeserializeメソッドを参照)。

export class Person {
  public name: string = null;
  public favoriteSites: string[] = [];

  private age: number = null;
  private id: number = null;
  private active: boolean;

  constructor(instanceData?: Person) {
    if (instanceData) {
      this.deserialize(instanceData);
    }
  }

  private deserialize(instanceData: Person) {
    // Note this.active will not be listed in keys since it's declared, but not defined
    const keys = Object.keys(this);

    for (const key of keys) {
      if (instanceData.hasOwnProperty(key)) {
        this[key] = instanceData[key];
      }
    }
  }
}

上の例では、単純に逆シリアル化メソッドを作成しました。実社会の例では、再利用可能な基本クラスまたはサービスメソッドに集中させます。

これはhttp応答のようなものでこれを利用する方法です...

this.http.get(ENDPOINT_URL)
  .map(res => res.json())
  .map((resp: Person) => new Person(resp) ) );

Tslint/ideが引数の型に互換性がないと文句を言う場合は、角括弧<YourClassName>を使用して引数を同じ型にキャストするだけです。例:

const person = new Person(<Person> { name: 'John', age: 35, id: 1 });

特定の型のクラスメンバー(別のクラスのインスタンス)がある場合は、getter/setterメソッドを使用してそれらを型付きインスタンスにキャストすることができます。

export class Person {
  private _acct: UserAcct = null;
  private _tasks: Task[] = [];

  // ctor & deserialize methods...

  public get acct(): UserAcct {
    return this.acct;
  }
  public set acct(acctData: UserAcct) {
    this._acct = new UserAcct(acctData);
  }

  public get tasks(): Task[] {
    return this._tasks;
  }

  public set tasks(taskData: Task[]) {
    this._tasks = taskData.map(task => new Task(task));
  }
}

上記の例では、acctとタスクのリストの両方をそれぞれのクラスインスタンスに逆シリアル化します。

15
Timothy Perez

私の場合はうまくいきます。 Object.assign(target、sources ...) を使いました。まず、正しいオブジェクトを作成してから、jsonオブジェクトからtargetにデータをコピーします。

let u:User = new User();
Object.assign(u , jsonUsers);

そしてより高度な使用例です。配列を使用した例.

this.someService.getUsers().then((users: User[]) => {
  this.users = [];
  for (let i in users) {
    let u:User = new User();
    Object.assign(u , users[i]);
    this.users[i] = u;
    console.log("user:" + this.users[i].id);
    console.log("user id from function(test it work) :" + this.users[i].getId());
  }

});

export class User {
  id:number;
  name:string;
  fullname:string;
  email:string;

  public getId(){
    return this.id;
  }
}
13
Adam111p

サーバから受け取ったJSONオブジェクトが期待された(読み取りがTypeScriptのインターフェースプロパティに準拠している)かどうかを自動的にチェックするものはまだありません。 ユーザー定義型ガード を使うことができます

次のインターフェースと愚かなjsonオブジェクトを考えてみましょう(それはどんな型であったかもしれません):

interface MyInterface {
    key: string;
 }

const json: object = { "key": "value" }

3つの可能な方法:

A.変数の後にアサーションまたは単純な静的キャストを入力します。

const myObject: MyInterface = json as MyInterface;

B.変数の前およびダイヤモンド間の単純な静的キャスト

const myObject: MyInterface = <MyInterface>json;

C.高度な動的キャスト、あなたは自分自身でオブジェクトの構造をチェックします

function isMyInterface(json: any): json is MyInterface {
    // silly condition to consider json as conform for MyInterface
    return typeof json.key === "string";
}

if (isMyInterface(json)) {
    console.log(json.key)
}
else {
        throw new Error(`Expected MyInterface, got '${json}'.`);
}

あなたは ここでこの例で遊ぶことができます

ここでの難点はisMyInterface関数を書くことです。 TSが遅かれ早かれ に複雑な入力をエクスポートする にデコレータを追加し、必要に応じてランタイムにオブジェクトの構造をチェックさせることを願っています。今のところ、目的がほぼ同じであるjsonスキーマバリデーターを使うことができますOR this ランタイム型チェック関数ジェネレータ

6
Flavien Volken

一言で言うとキャストではありませんが。 https://github.com/JohnWhiteTB/TypedJSON が有用な代替手段であることがわかりました。

@JsonObject
class Person {
    @JsonMember
    firstName: string;

    @JsonMember
    lastName: string;

    public getFullname() {
        return this.firstName + " " + this.lastName;
    }
}
var person = TypedJSON.parse('{ "firstName": "John", "lastName": "Doe" }', Person);

person instanceof Person; // true
person.getFullname(); // "John Doe"
2
Neil

ほとんど正解ですが、あまり効率的ではない答えを含む古い質問です。これが私が提案していることです。

init() methodおよび静的キャストメソッド(単一のオブジェクトおよび配列用)を含む基本クラスを作成します。静的メソッドはどこでも構いません。基本クラスと init() のバージョンはその後の簡単な拡張を可能にします。

export class ContentItem {
    // parameters: doc - plain JS object, proto - class we want to cast to (subclass of ContentItem)
    static castAs<T extends ContentItem>(doc: T, proto: typeof ContentItem): T {
        // if we already have the correct class skip the cast
        if (doc instanceof proto) { return doc; }
        // create a new object (create), and copy over all properties (assign)
        const d: T = Object.create(proto.prototype);
        Object.assign(d, doc);
        // reason to extend the base class - we want to be able to call init() after cast
        d.init(); 
        return d;
    }
    // another method casts an array
    static castAllAs<T extends ContentItem>(docs: T[], proto: typeof ContentItem): T[] {
        return docs.map(d => ContentItem.castAs(d, proto));
    }
    init() { }
}

@/Adam111pの投稿で同様の仕組み( assign() 付き)が言及されています。もう1つの(より完全な)方法です。 @Timothy Perezは assign() に批判的ですが、ここでは完全に適切です。

派生(実際の)クラスを実装します。

import { ContentItem } from './content-item';

export class SubjectArea extends ContentItem {
    id: number;
    title: string;
    areas: SubjectArea[]; // contains embedded objects
    depth: number;

    // method will be unavailable unless we use cast
    lead(): string {
        return '. '.repeat(this.depth);
    }

    // in case we have embedded objects, call cast on them here
    init() {
        if (this.areas) {
            this.areas = ContentItem.castAllAs(this.areas, SubjectArea);
        }
    }
}

これで、サービスから取得したオブジェクトをキャストできます。

const area = ContentItem.castAs<SubjectArea>(docFromREST, SubjectArea);

SubjectArea オブジェクトのすべての階層は正しいクラスになります。

ユースケース/例Angularサービスを作成します(ここでも抽象基本クラス)。

export abstract class BaseService<T extends ContentItem> {
  BASE_URL = 'http://Host:port/';
  protected abstract http: Http;
  abstract path: string;
  abstract subClass: typeof ContentItem;

  cast(source: T): T {
    return ContentItem.castAs(source, this.subClass);
  }
  castAll(source: T[]): T[] {
    return ContentItem.castAllAs(source, this.subClass);
  }

  constructor() { }

  get(): Promise<T[]> {
    const value = this.http.get(`${this.BASE_URL}${this.path}`)
      .toPromise()
      .then(response => {
        const items: T[] = this.castAll(response.json());
        return items;
      });
    return value;
  }
}

使い方はとても簡単になります。エリアサービスを作成します。

@Injectable()
export class SubjectAreaService extends BaseService<SubjectArea> {
  path = 'area';
  subClass = SubjectArea;

  constructor(protected http: Http) { super(); }
}

get() サービスのメソッドは SubjectArea objectsとしてキャストされた配列のPromiseを返す - (階層全体)

今、別のクラスがあるとしましょう。

export class OtherItem extends ContentItem {...}

データを取得して正しいクラスにキャストするサービスを作成するのは簡単です。

@Injectable()
export class OtherItemService extends BaseService<OtherItem> {
  path = 'other';
  subClass = OtherItem;

  constructor(protected http: Http) { super(); }
}

これを行う簡単なツールを作成しました https://beshanoe.github.io/json2ts/ json2ts.comとは異なり、ブラウザで直接機能し、未知のサーバーにデータを送信しません。また複数の設定があります。機能を向上させるために努力します

1
beshanoe

Lates TSでは、このようにすることができます。

const isMyInterface = (val: any): val is MyInterface => {
  if (!val) { return false; }
  if (!val.myProp) { return false; }
  return true;
};

そしてこのようなユーザーより:

if (isMyInterface(data)) {
 // now data will be type of MyInterface
}
0
Jaroslav

私は同様のニーズに遭遇しました。特定のクラス定義へのREST api呼び出しからのJSONへの、またはJSONからの簡単な変換を可能にするものが必要でした。私が見つけた解決策は不十分であるか、クラスのコードを書き直して注釈などを追加することを意図していました。

JSONオブジェクトとの間でクラスをシリアライズ/デシリアライズするために、GSONがJavaで使用されるようなものが必要でした。

コンバーターがJSでも機能するという後のニーズと合わせて、私は自分のパッケージの作成を終了しました。

ただし、オーバーヘッドが少しあります。しかし、開始時には、追加と編集に非常に便利です。

モジュールを初期化するには:

  1. 変換スキーマ-フィールド間をマッピングし、変換の実行方法を決定できます
  2. クラスマップ配列
  3. 変換関数マップ-特別な変換用。

次に、コードで、次のような初期化されたモジュールを使用します。

const convertedNewClassesArray : MyClass[] = this.converter.convert<MyClass>(jsonObjArray, 'MyClass');

const convertedNewClass : MyClass = this.converter.convertOneObject<MyClass>(jsonObj, 'MyClass');

または、JSONに:

const jsonObject = this.converter.convertToJson(myClassInstance);

Npmパッケージへのこのリンクと、モジュールの使用方法の詳細な説明を使用してください。 json-class-converter

またのためにそれを包んだ
での角度の使用: angular-json-class-converter

0
Doronm

私はここでこのライブラリを使用しました: https://github.com/pleerock/class-transformer

<script lang="ts">
    import { plainToClass } from 'class-transformer';
</script>

実装:

private async getClassTypeValue() {
  const value = await plainToClass(ProductNewsItem, JSON.parse(response.data));
}

JSON形式のデータであることを理解するために、plainToClassのJSON値を解析する必要がある場合があります。

0
Mac Chibueze

私たちが.NETショップなので私たちがしたことの1つは、このツールを作成することです( https://github.com/Centeva/TypeScripter )。

それは私たちのDLLからTypeScriptクラスを構築します。私たちがしなければならないのは、私たちの応答jsonをパラメータとしてクラスに渡すことです。それは私達のために滑らかに動作します。

0
Devcon

Json-TypeScript-mapperについての言及はありません: https://www.npmjs.com/package/json-TypeScript-mapper 。私が言うことができる限り、それは@ PhilipMiglinciの発見と@ Pakの答えの組み合わせのように見えます。

0
BBaysinger

フロントエンドでAngular 6を使用し、バックエンドでJavaオブジェクトを返すSpring Bootアプリケーションを使用しています。する必要があるのは、一致するプロパティを持つangularアプリケーションで同様のクラスを定義することです。その後、オブジェクトをangularクラスオブジェクト(comp as Company以下の例で)。

例については、以下のフロントエンドコードを参照してください。もっと明確にする必要がある場合は、コメントでお知らせください。

  createCompany() {
    let company = new Company();
    company.name = this.companyName;

    this.companyService.createCompany(company).subscribe(comp => {
       if (comp !== null) {
        this.employerAdminCompany = comp as Company;
      }
    });
  }

companyは、スプリングブートアプリのエンティティオブジェクトであり、Angularのクラスでもあります。

export class Company {
    public id: number;
    public name: string;
    public owner: User;
    public locations: Array<Location>;
}
0
Maneesh Parihar

あなたはあなたの型のinterfaceSomeType)を作成し、その中にオブジェクトをキャストすることができます。

const typedObject: SomeType = <SomeType> responseObject;
0
Jayant Varshney