JSON в TypeScript экземпляр класса?

Я провел довольно много исследований, но я не вполне доволен тем, что нашел. Чтобы быть уверенным здесь, мой вопрос: Что на самом деле является самым надежным и элегантным автоматизированным решением для десериализации JSON для TypeScript экземпляров класса времени выполнения?

Скажем, у меня этот класс:

class Foo {
  name: string;
  GetName(): string { return this.name };
}

И скажу, что я получил эту строку JSON для десериализации:

{"name": "John Doe"}

Какое лучшее и наиболее удобное решение для получения экземпляра класса Foo с именем, установленным в "John Doe" и методом GetName()? Я прошу очень конкретно, потому что я знаю, что легко десериализовать чистый объект данных. Мне интересно, можно ли получить экземпляр класса с рабочими методами без необходимости ручного разбора или любого ручного копирования данных. Если полностью автоматическое решение невозможно, то какое следующее лучшее решение?

Ответ 1

Этот вопрос довольно широк, поэтому я собираюсь дать пару решений.

Решение 1: Вспомогательный метод

Вот пример использования метода Помощника, который вы можете изменить в соответствии с вашими потребностями:

class SerializationHelper {
    static toInstance<T>(obj: T, json: string) : T {
        var jsonObj = JSON.parse(json);

        if (typeof obj["fromJSON"] === "function") {
            obj["fromJSON"](jsonObj);
        }
        else {
            for (var propName in jsonObj) {
                obj[propName] = jsonObj[propName]
            }
        }

        return obj;
    }
}

Затем, используя его:

var json = '{"name": "John Doe"}',
    foo = SerializationHelper.toInstance(new Foo(), json);

foo.GetName() === "John Doe";

Расширенная десериализация

Это также может позволить некоторую пользовательскую десериализацию, добавив свой собственный метод fromJSON к классу (это хорошо работает с тем, как JSON.stringify уже использует метод toJSON, как будет показано):

interface IFooSerialized {
    nameSomethingElse: string;
}

class Foo {
  name: string;
  GetName(): string { return this.name }

  toJSON(): IFooSerialized {
      return {
          nameSomethingElse: this.name
      };
  }

  fromJSON(obj: IFooSerialized) {
        this.name = obj.nameSomethingElse;
  }
}

Затем, используя его:

var foo1 = new Foo();
foo1.name = "John Doe";

var json = JSON.stringify(foo1);

json === '{"nameSomethingElse":"John Doe"}';

var foo2 = SerializationHelper.toInstance(new Foo(), json);

foo2.GetName() === "John Doe";

Решение 2: базовый класс

Другой способ, которым вы могли бы это сделать, - создать собственный базовый класс:

class Serializable {
    fillFromJSON(json: string) {
        var jsonObj = JSON.parse(json);
        for (var propName in jsonObj) {
            this[propName] = jsonObj[propName]
        }
    }
}

class Foo extends Serializable {
    name: string;
    GetName(): string { return this.name }
}

Затем, используя его:

var foo = new Foo();
foo.fillFromJSON(json);

Слишком много разных способов реализации пользовательской десериализации с использованием базового класса, поэтому я оставлю это до того, как вы этого захотите.

Ответ 2

Теперь вы можете использовать Object.assign(target, ...sources). Следуя вашему примеру, вы можете использовать его следующим образом:

class Foo {
  name: string;
  getName(): string { return this.name };
}

let fooJson: string = '{"name": "John Doe"}';
let foo: Foo = Object.assign(new Foo(), JSON.parse(fooJson));

console.log(foo.getName()); //returns John Doe

Object.assign является частью ECMAScript 2015 и в настоящее время доступен в большинстве современных браузеров.

Ответ 3

На самом деле это самое надежное и элегантное автоматизированное решение для десериализации JSON для TypeScript экземпляров класса времени выполнения?

Используя декораторы свойств с ReflectDecorators, чтобы записывать информацию типа времени, доступного во время выполнения, которая может использоваться во время процесс десериализации обеспечивает удивительно чистый и широко адаптируемый подход, который прекрасно вписывается в существующий код. Он также полностью автоматизирован и также работает для вложенных объектов.

Реализация этой идеи TypedJSON, которую я создал именно для этой задачи:

@JsonObject
class Foo {
    @JsonMember
    name: string;

    getName(): string { return this.name };
}
var foo = TypedJSON.parse('{"name": "John Doe"}', Foo);

foo instanceof Foo; // true
foo.getName(); // "John Doe"

Ответ 4

Почему бы вам не просто сделать что-то подобное?

class Foo {
  constructor(myObj){
     Object.assign(this, myObj);
  }
  get name() { return this._name; }
  set name(v) { this._name = v; }
}

let foo = new Foo({ name: "bat" });
foo.toJSON() //=> your json ...

Ответ 5

Лучшее решение, которое я нашел при работе с Typescript классами и объектами json: добавьте конструктор в ваш класс Typescript, который принимает данные json в качестве параметра. В этом конструкторе вы расширяете свой json-объект с помощью jQuery, например: $.extend(this, jsonData). $.extend позволяет сохранять прототипы javascript при добавлении свойств объекта json.

export class Foo
{
    Name: string;
    getName(): string { return this.Name };

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

В своем обратном вызове ajax переведите jsons в свой объект Typescript следующим образом:

onNewFoo( jsonFoos : any[] )
{
  let receviedFoos = $.map( jsonFoos, (json) => { return new Foo( json ); } );

  // then call a method:
  let firstFooName = receviedFoos[0].GetName();
}

Если вы не добавите конструктор, juste вызовите ваш обратный вызов ajax:

let newFoo = new Foo();
$.extend( newFoo, jsonData);
let name = newFoo.GetName()

... но конструктор будет полезен, если вы хотите также преобразовать объект json для детей. См. Мой подробный ответ здесь.