JSON 객체로 TypeScript 객체를 초기화하는 방법


199

REST 서버에 대한 AJAX 호출에서 JSON 객체를받습니다. 이 객체에는 TypeScript 클래스와 일치하는 속성 이름이 있습니다 ( 이 질문 에 대한 후속 조치입니다 ).

그것을 초기화하는 가장 좋은 방법은 무엇입니까? 클래스 (& JSON 객체)에는 객체 목록 인 멤버와 클래스 인 멤버가 있고 해당 클래스에는 목록 및 / 또는 클래스 인 멤버가 있기 때문에 이것이 효과 가 있다고 생각하지 않습니다 .

그러나 멤버 이름을 조회하고 필요에 따라 목록을 만들고 클래스를 인스턴스화하는 방법을 선호하므로 모든 클래스의 모든 멤버에 대해 명시 적 코드를 작성할 필요가 없습니다 (LOT이 있습니다!)


1
왜 다시 질문 했습니까? (다른 질문에서 제공 한 답변에서 작동하지 않으며 속성을 기존 객체에 복사하는 것과 관련이 있다고 말 했으므로)?
WiredPrairie


3
@WiredPrairie이 질문은 다릅니다. 속성을 하나씩 걸어서 할당 할 수 있는지 묻습니다. 다른 질문은 내가 던질 수 있는지 묻고있었습니다.
David Thielen

1
@WiredPrairie cont : 프리미티브 유형에 도달 할 때까지 계속해서 속성에 다이빙을하면 해당 속성을 가로 질러 할당 할 수 있습니다.
David Thielen

2
내가 제안한 것처럼 여전히 모든 값을 복사하고 있습니다. JavaScript의 기본 디자인이기 때문에 TypeScript에서는이를 수행 할 수있는 새로운 방법이 없습니다. 큰 개체의 경우 값을 복사하지 않고 데이터 구조를 대신 "동작"할 수 있습니다.
WiredPrairie

답변:


189

이것들은 몇 가지 다른 방법을 보여주기 위해 이것에 대한 빠른 샷입니다. 그것들은 결코 "완전한"것이 아니며, 면책 조항으로, 이렇게하는 것이 좋은 생각이라고 생각하지 않습니다. 또한 코드를 함께 빨리 입력했기 때문에 코드가 너무 깨끗하지 않습니다.

또한 참고 : 물론 역 직렬화 가능 클래스에는 모든 종류의 역 직렬화를 알고있는 다른 모든 언어의 경우와 마찬가지로 기본 생성자가 있어야합니다. 물론, 인수없이 기본이 아닌 생성자를 호출해도 Javascript는 불만을 제기하지 않지만 클래스를 준비하는 것이 좋습니다.

옵션 # 1 : 런타임 정보가 전혀 없음

이 방법의 문제점은 대부분 멤버의 이름이 해당 클래스와 일치해야한다는 것입니다. 클래스별로 같은 유형의 멤버로 자동 제한하고 여러 가지 모범 사례 규칙을 위반합니다. 나는 이것에 대해 강력히 권고하지만,이 답변을 썼을 때 처음으로 "초안"이었기 때문에 여기에 적어 두십시오.

module Environment {
    export class Sub {
        id: number;
    }

    export class Foo {
        baz: number;
        Sub: Sub;
    }
}

function deserialize(json, environment, clazz) {
    var instance = new clazz();
    for(var prop in json) {
        if(!json.hasOwnProperty(prop)) {
            continue;
        }

        if(typeof json[prop] === 'object') {
            instance[prop] = deserialize(json[prop], environment, environment[prop]);
        } else {
            instance[prop] = json[prop];
        }
    }

    return instance;
}

var json = {
    baz: 42,
    Sub: {
        id: 1337
    }
};

var instance = deserialize(json, Environment, Environment.Foo);
console.log(instance);

옵션 # 2 : 이름 속성

옵션 # 1의 문제를 해결하려면 JSON 객체의 노드 유형에 대한 정보가 필요합니다. 문제는 Typescript에서 이러한 것들이 컴파일 타임 구조이며 런타임에 필요하다는 것입니다. 그러나 런타임 객체는 속성이 설정 될 때까지 속성을 인식하지 못합니다.

이를 수행하는 한 가지 방법은 클래스에 이름을 알리는 것입니다. 하지만 JSON에서도이 속성이 필요합니다. 실제로, 당신 은 json 에서만 필요합니다 :

module Environment {
    export class Member {
        private __name__ = "Member";
        id: number;
    }

    export class ExampleClass {
        private __name__ = "ExampleClass";

        mainId: number;
        firstMember: Member;
        secondMember: Member;
    }
}

function deserialize(json, environment) {
    var instance = new environment[json.__name__]();
    for(var prop in json) {
        if(!json.hasOwnProperty(prop)) {
            continue;
        }

        if(typeof json[prop] === 'object') {
            instance[prop] = deserialize(json[prop], environment);
        } else {
            instance[prop] = json[prop];
        }
    }

    return instance;
}

var json = {
    __name__: "ExampleClass",
    mainId: 42,
    firstMember: {
        __name__: "Member",
        id: 1337
    },
    secondMember: {
        __name__: "Member",
        id: -1
    }
};

var instance = deserialize(json, Environment);
console.log(instance);

옵션 # 3 : 명시 적으로 멤버 유형을 명시

위에서 언급했듯이 클래스 멤버의 유형 정보는 런타임에 사용할 수 없습니다. 즉, 사용 가능하지 않은 경우입니다. 우리는 기본이 아닌 멤버에게만이 작업을 수행하면되며 다음과 같이 진행하면됩니다.

interface Deserializable {
    getTypes(): Object;
}

class Member implements Deserializable {
    id: number;

    getTypes() {
        // since the only member, id, is primitive, we don't need to
        // return anything here
        return {};
    }
}

class ExampleClass implements Deserializable {
    mainId: number;
    firstMember: Member;
    secondMember: Member;

    getTypes() {
        return {
            // this is the duplication so that we have
            // run-time type information :/
            firstMember: Member,
            secondMember: Member
        };
    }
}

function deserialize(json, clazz) {
    var instance = new clazz(),
        types = instance.getTypes();

    for(var prop in json) {
        if(!json.hasOwnProperty(prop)) {
            continue;
        }

        if(typeof json[prop] === 'object') {
            instance[prop] = deserialize(json[prop], types[prop]);
        } else {
            instance[prop] = json[prop];
        }
    }

    return instance;
}

var json = {
    mainId: 42,
    firstMember: {
        id: 1337
    },
    secondMember: {
        id: -1
    }
};

var instance = deserialize(json, ExampleClass);
console.log(instance);

옵션 # 4 : 장황하지만 깔끔한 방법

2016 년 1 월 3 일 업데이트 : @GameAlchemist 가 Typescript 1.7 의 의견 ( idea , implementation ) 에서 지적했듯이 아래 설명 된 솔루션은 클래스 / 속성 데코레이터를 사용하여 더 나은 방법으로 작성할 수 있습니다.

직렬화는 항상 문제이며 제 생각에 가장 좋은 방법은 가장 짧지 않은 방법입니다. 모든 옵션 중에서 클래스의 작성자가 deserialized 객체의 상태를 완전히 제어 할 수 있기 때문에 이것이 내가 선호하는 것입니다. 내가 추측해야한다면, 다른 모든 옵션이 조만간 당신을 곤경에 빠뜨릴 것이라고 말하고 싶습니다 (Javascript가 이것을 다루는 기본 방법을 제시하지 않는 한).

실제로 다음 예제는 유연성 정의를 수행하지 않습니다. 실제로 클래스의 구조를 복사합니다. 그러나 여기서 명심해야 할 차이점은 클래스가 전체 클래스의 상태를 제어하려는 모든 종류의 JSON을 사용하도록 모든 권한을 가지고 있다는 것입니다 (사물 등을 계산할 수 있음).

interface Serializable<T> {
    deserialize(input: Object): T;
}

class Member implements Serializable<Member> {
    id: number;

    deserialize(input) {
        this.id = input.id;
        return this;
    }
}

class ExampleClass implements Serializable<ExampleClass> {
    mainId: number;
    firstMember: Member;
    secondMember: Member;

    deserialize(input) {
        this.mainId = input.mainId;

        this.firstMember = new Member().deserialize(input.firstMember);
        this.secondMember = new Member().deserialize(input.secondMember);

        return this;
    }
}

var json = {
    mainId: 42,
    firstMember: {
        id: 1337
    },
    secondMember: {
        id: -1
    }
};

var instance = new ExampleClass().deserialize(json);
console.log(instance);

12
옵션 # 4는 내가 합리적인 방법으로 부르는 것입니다. 직렬화 해제 코드를 작성해야하지만 동일한 클래스에 있으며 완전히 제어 할 수 있습니다. Java에서 온 경우 이것은 작성 equals하거나 toString메소드 를 작성하는 것과 비슷 합니다 (일반적으로 자동 생성되는 것만). 원하는 경우 생성기를 작성하는 것이 너무 어렵지 deserialize않지만 런타임 자동화 일 수는 없습니다.
Ingo Bürk

2
@ IngoBürk, 나는 2 년 후이 질문을하고 있다는 것을 알고 있지만 어떻게 객체 배열에서 작동합니까? 위의 샘플 코드는 JSON 객체에 적합합니다. 객체 배열에 어떻게 사용할 수 있습니까?
Pratik Gaikwad

2
참고 사항 : 1.7 이후 (답변보다 최근에 나온 것) typescript는 클래스 / 속성 데코레이터를 제공하여 네 번째 솔루션을 더 깔끔하게 작성할 수 있습니다.
GameAlchemist

1
내가 찾은 최고의 문서는 StackOverflow 답변입니다 : stackoverflow.com/a/29837695/856501 . 나는 내 프로젝트에서 데코레이터를 사용했으며, 몇 가지 다른 기능을 원하지만 매력처럼 작동한다고 말해야합니다.
GameAlchemist

2
나는 아직 프로덕션 프로젝트의 데코레이터로 뛰어 들지 않을 것입니다-그들은 여전히 ​​실험적인 기능이라는 것을 명심하십시오. 우리가 우려하는 한 다음 버전에서는 사라질 수 있고 많은 코드를 다시 작성하거나 구식 TS 버전에 영원히 붙어 있어야하기 때문에 실제 코드를 "실험"에 기반하지 않습니다. 그냥 내 $ .02
RVP

35

당신이 사용할 수있는 Object.assign이가, 나는 현재 타이프 라이터 2.0.2 사용하고 추가되었을 때 나도 몰라,이 나타난다는 ES6 기능이 될 수 있습니다.

client.fetch( '' ).then( response => {
        return response.json();
    } ).then( json => {
        let hal : HalJson = Object.assign( new HalJson(), json );
        log.debug( "json", hal );

여기에 HalJson

export class HalJson {
    _links: HalLinks;
}

export class HalLinks implements Links {
}

export interface Links {
    readonly [text: string]: Link;
}

export interface Link {
    readonly href: URL;
}

다음은 크롬이 말하는 것입니다

HalJson {_links: Object}
_links
:
Object
public
:
Object
href
:
"http://localhost:9000/v0/public

재귀 적으로 할당하지 않는 것을 볼 수 있습니다.


2
기본적으로 다음과 같습니다 Object.assign.. 왜 우리는이 질문 위에 두 개의 어휘 같은 답을 가지고 있습니까?
phil294

18
@Blauhim 왜냐하면 Object.assign재귀 적으로 작동하지 않고 올바른 객체 유형을 인스턴스화하지 않고 값을 Object인스턴스 로 남겨 둡니다 . 사소한 작업에는 적합하지만 복잡한 유형 직렬화는 불가능합니다. 예를 들어 클래스 속성이 사용자 정의 클래스 유형 인 경우 JSON.parse+ Object.assign는 해당 속성을로 인스턴스화합니다 Object. 부작용에는 누락 된 방법 및 접근자가 포함됩니다.
John Weisz

@JohnWeisz 객체 할당의 최상위 클래스에는 올바른 유형이 있으며, 이것에서 재귀 적 인 것을 언급했습니다 ... YMMV라고 말하면 거래 차단기 일 수 있습니다.
xenoterracide

질문에서 직접 인용했다 : "클래스는 클래스의 객체와 멤버의리스트 인 멤버를 가지고 있고, 그 클래스는리스트와 / 또는 클래스 인 멤버를 가지고있다 ...] 멤버를 찾는 접근법을 선호한다 이름과에서 양수인을, 목록을 작성하고 필요에 따라 클래스를 인스턴스화 내가 모든 클래스의 모든 구성원에 대한 명시 적 코드를 작성할 필요가 없습니다 " -를 가진 경우가 아니라 Object.assign여전히 의해 중첩 인스턴스를 작성하는 온다, 손. 이 방법은 매우 간단한 튜토리얼 수준의 객체에는 적합하지만 실제 사용에는 적합하지 않습니다.
John Weisz

@ JohnWeisz는 확실히 대답하지 않았으며 일부 사용 사례에서는 단순 해 보였기 때문에 대부분이 대답했습니다. 나는 그것이 당신이 찾고있는 것을하기 위해 반사와 같은 다른 답변과 함께 사용될 수 있다고 확신합니다. 또한 나중에 기억할 수 있도록 부분적으로 썼습니다. 이러한 답변을보고 훨씬 더 강력한 라이브러리를 사용하고 작성한 것은 "실제로"사용할 수있는 것으로 보이지 않습니다.
xenoterracide

34

TLDR : TypedJSON (작업 개념 증명)


이 문제의 복잡성의 근본 원인은 컴파일 타임 에만 존재하는 유형 정보를 사용하여 런타임시 JSON을 직렬화 해제해야한다는 것 입니다. 이를 위해서는 런타임에 형식 정보를 사용할 수 있어야합니다.

다행히도 데코레이터ReflectDecorators 를 사용하면 매우 우아하고 강력한 방법으로 해결할 수 있습니다 .

  1. 직렬화 대상 속성에 속성 데코레이터 를 사용 하여 메타 데이터 정보를 기록하고 해당 정보를 클래스 프로토 타입과 같은 곳에 저장
  2. 이 메타 데이터 정보를 재귀 이니셜 라이저 (디시리얼라이저)에 제공

 

녹화 유형 정보

ReflectDecorators 와 속성 데코레이터를 조합하면 속성에 대한 유형 정보를 쉽게 기록 할 수 있습니다. 이 접근 방식의 기본 구현은 다음과 같습니다.

function JsonMember(target: any, propertyKey: string) {
    var metadataFieldKey = "__propertyTypes__";

    // Get the already recorded type-information from target, or create
    // empty object if this is the first property.
    var propertyTypes = target[metadataFieldKey] || (target[metadataFieldKey] = {});

    // Get the constructor reference of the current property.
    // This is provided by TypeScript, built-in (make sure to enable emit
    // decorator metadata).
    propertyTypes[propertyKey] = Reflect.getMetadata("design:type", target, propertyKey);
}

주어진 속성에 대해 위의 스 니펫은 속성의 생성자 함수에 대한 참조를 __propertyTypes__클래스 프로토 타입 의 숨겨진 속성에 추가합니다. 예를 들면 다음과 같습니다.

class Language {
    @JsonMember // String
    name: string;

    @JsonMember// Number
    level: number;
}

class Person {
    @JsonMember // String
    name: string;

    @JsonMember// Language
    language: Language;
}

이제 런타임에 필요한 유형 정보를 얻었으므로 이제 처리 할 수 ​​있습니다.

 

처리 유형 정보

먼저 다음을 사용하여 Object인스턴스 를 가져와야합니다. JSON.parse그 후에는 __propertyTypes__위에서 수집 한 전체를 반복 하고 필요한 속성을 인스턴스화 할 수 있습니다. deserializer에 시작점이 있도록 루트 개체의 유형을 지정해야합니다.

다시 말하지만,이 접근법의 간단한 구현은 다음과 같습니다

function deserialize<T>(jsonObject: any, Constructor: { new (): T }): T {
    if (!Constructor || !Constructor.prototype.__propertyTypes__ || !jsonObject || typeof jsonObject !== "object") {
        // No root-type with usable type-information is available.
        return jsonObject;
    }

    // Create an instance of root-type.
    var instance: any = new Constructor();

    // For each property marked with @JsonMember, do...
    Object.keys(Constructor.prototype.__propertyTypes__).forEach(propertyKey => {
        var PropertyType = Constructor.prototype.__propertyTypes__[propertyKey];

        // Deserialize recursively, treat property type as root-type.
        instance[propertyKey] = deserialize(jsonObject[propertyKey], PropertyType);
    });

    return instance;
}
var json = '{ "name": "John Doe", "language": { "name": "en", "level": 5 } }';
var person: Person = deserialize(JSON.parse(json), Person);

위의 아이디어는 JSON에있는 것 대신 예상되는 유형 (복잡한 / 객체 값의 경우)에 의한 직렬화 해제의 큰 이점을 가지고 있습니다. Person예상되는 경우 Person생성 된 인스턴스입니다. 기본 유형 및 배열에 대한 몇 가지 추가 보안 조치를 사용하면 악의적 인 JSON에 저항 하는 이 접근 방식을 안전하게 만들 수 있습니다 .

 

가장자리 케이스

당신의 해결책이 지금 행복 경우, 간단한, 나쁜 소식을하십시오이 광대 한 처리를 수행해야합니다 가장자리의 경우의 수는. 그중 일부만 :

  • 배열 및 배열 요소 (특히 중첩 배열)
  • 다형성
  • 추상 클래스와 인터페이스
  • ...

당신은이 모든 주위에 바이올린하지 않으려면, 나는이 방법을 사용하는 개념 증명의 작동 실험 버전을 추천하고 기쁠 것 (나는 그렇지 내기), TypedJSON - 내가 만든 이 정확한 문제를 해결하기 위해 제가 매일 직면하는 문제입니다.

데코레이터가 여전히 실험적으로 간주되는 방식으로 인해 프로덕션 용도로 사용하는 것은 좋지 않지만 지금까지는 잘 작동했습니다.


TypedJSON은 훌륭하게 작동했습니다. 참조 주셔서 감사합니다.
Neil

대단한 일, 당신은 한동안 나를 괴롭힌 문제에 대한 매우 우아한 해결책을 생각해 냈습니다. 나는 당신의 프로젝트를 매우 밀접하게 따를 것입니다!
John Strickler

12

나는이 사람을 사용하여 일을 해왔다 : https://github.com/weichx/cerialize

매우 간단하지만 강력합니다. 다음을 지원합니다.

  • 전체 객체 트리의 직렬화 및 역 직렬화
  • 동일한 객체의 지속적 및 일시적 속성.
  • 직렬화 해제 논리를 사용자 정의하기위한 후크입니다.
  • 기존 인스턴스로 직렬화 (직렬화)하거나 (앵귤러에 적합) 새 인스턴스를 생성 할 수 있습니다.
  • 기타

예:

class Tree {
  @deserialize public species : string; 
  @deserializeAs(Leaf) public leafs : Array<Leaf>;  //arrays do not need extra specifications, just a type.
  @deserializeAs(Bark, 'barkType') public bark : Bark;  //using custom type and custom key name
  @deserializeIndexable(Leaf) public leafMap : {[idx : string] : Leaf}; //use an object as a map
}

class Leaf {
  @deserialize public color : string;
  @deserialize public blooming : boolean;
  @deserializeAs(Date) public bloomedAt : Date;
}

class Bark {
  @deserialize roughness : number;
}

var json = {
  species: 'Oak',
  barkType: { roughness: 1 },
  leafs: [ {color: 'red', blooming: false, bloomedAt: 'Mon Dec 07 2015 11:48:20 GMT-0500 (EST)' } ],
  leafMap: { type1: { some leaf data }, type2: { some leaf data } }
}
var tree: Tree = Deserialize(json, Tree);

6

나는 타이프 라이터 인터페이스와 결과에 대한 런타임 유형 검사를 수행하기위한 실행 "형태 맵"을 생성하는 도구를 만들었습니다 JSON.parse: ts.quicktype.io를

예를 들어 다음과 같은 JSON이 제공됩니다.

{
  "name": "David",
  "pets": [
    {
      "name": "Smoochie",
      "species": "rhino"
    }
  ]
}

quicktype 은 다음 TypeScript 인터페이스 및 유형 맵을 생성합니다.

export interface Person {
    name: string;
    pets: Pet[];
}

export interface Pet {
    name:    string;
    species: string;
}

const typeMap: any = {
    Person: {
        name: "string",
        pets: array(object("Pet")),
    },
    Pet: {
        name: "string",
        species: "string",
    },
};

그런 다음 JSON.parse유형 맵과 결과를 확인합니다 .

export function fromJson(json: string): Person {
    return cast(JSON.parse(json), object("Person"));
}

코드를 생략 했지만 세부 정보를 보려면 빠른 유형 을 사용해보십시오 .


1
많은 시간을 연구하고 몇 가지 파싱 기술을 시도한 후에, 나는 이것이 데코레이터가 여전히 실험적이기 때문에 훌륭한 솔루션이라고 말할 수 있습니다. * 원래의 링크는 저를 위해 깨졌습니다. 그러나 ts.quicktype.io 는 작동합니다. * JSON을 JSON 스키마로 변환하는 것이 좋은 첫 단계입니다.
LexieHankins

3

옵션 # 5 : 타입 스크립트 생성자와 jQuery.extend 사용

이것은 가장 유지 관리 가능한 방법 인 것 같습니다 : json 구조를 매개 변수로 사용하는 생성자를 추가하고 json 객체를 확장하십시오. 그렇게하면 json 구조를 전체 응용 프로그램 모델로 구문 분석 할 수 있습니다.

인터페이스를 만들거나 생성자에 속성을 나열 할 필요가 없습니다.

export class Company
{
    Employees : Employee[];

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

        // apply the same principle to linked objects:
        if ( jsonData.Employees )
            this.Employees = jQuery.map( jsonData.Employees , (emp) => {
                return new Employee ( emp );  });
    }

    calculateSalaries() : void { .... }
}

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

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

        // case where your object's property does not match the json's:
        this.city = jsonData.town;
    }
}

급여를 계산할 회사를받는 Ajax 콜백에서 :

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

   // call the methods on your newCompany object ...
   newCompany.calculateSalaries()
}

어디서 $.extend왔어요?
whale_steward

@ whale_steward 저자가 jQuery 라이브러리를 참조한다고 가정합니다. JavaScript 세계에서 '$'는 종종 jQuery를 사용하는 사람입니다.
Nick Roth

가져 오는 방법? html 헤드에 포함하면 충분합니까?
whale_steward

예, $를 jQuery로 바꾸려면 답변을 업데이트하십시오. html 헤드에서 jQuery.js를 가져오고 package.json, devDependencies 섹션에 @ types / jquery를 설치하고 추가하십시오.
Anthony Brenelière

1
Javascript에서는 Object.assignjQuery에 대한 이러한 종속성을 제거 해야 합니다.
레온 펠티에

2

간단한 객체의 경우이 방법이 좋습니다.

class Person {
  constructor(
    public id: String, 
    public name: String, 
    public title: String) {};

  static deserialize(input:any): Person {
    return new Person(input.id, input.name, input.title);
  }
}

var person = Person.deserialize({id: 'P123', name: 'Bob', title: 'Mr'});

생성자에서 속성을 정의하는 기능을 활용하면 간결 해집니다.

이렇게하면 입력 된 객체 (Object.assign 또는 객체를 제공하는 일부 변형을 사용하는 모든 답변)와 외부 라이브러리 또는 데코레이터가 필요하지 않습니다.


1

위에서 설명한 네 번째 옵션은 간단하고 좋은 방법입니다. 예를 들어 하위 계층에서 발생하는 멤버 목록과 같은 클래스 계층 구조를 처리해야하는 경우 두 번째 옵션과 결합해야합니다. 예를 들어 Director는 회원을 연장하거나 Student는 회원을 연장합니다. 이 경우 서브 클래스 유형을 json 형식으로 제공해야합니다


1

JQuery .extend가이를 대신합니다.

var mytsobject = new mytsobject();

var newObj = {a:1,b:2};

$.extend(mytsobject, newObj); //mytsobject will now contain a & b

1

이 목적을 위해 내가 찾은 최고는 클래스 트랜스포머입니다. github.com/typestack/class-transformer

그것이 당신이 그것을 사용하는 방법입니다 :

일부 수업 :

export class Foo {

    name: string;

    @Type(() => Bar)
    bar: Bar;

    public someFunction = (test: string): boolean => {
        ...
    }
}


import { plainToClass } from 'class-transformer';

export class SomeService {

  anyFunction() {
u = plainToClass(Foo, JSONobj);
 }

@Type 데코레이터를 사용하면 중첩 속성도 만들어집니다.


0

실제는 아니지만 간단한 해결책 일 수 있습니다.

interface Bar{
x:number;
y?:string; 
}

var baz:Bar = JSON.parse(jsonString);
alert(baz.y);

어려운 의존성에 대해서도 작업하십시오!


9
이 방법은 실제로 예상대로 작동하지 않습니다. 당신은 실행 결과를 검사하는 경우 baz유형이 될 것입니다 Object및 입력하지 Bar.때문에이 간단한 경우에 작동하는 Bar어떤 방법 (단지 원시적 인 특성)이 없습니다. Bar와 같은 메소드가있는 경우 isEnabled()해당 메소드가 직렬화 된 JSON 문자열에 없으므로이 방법이 실패합니다.
Todd

0

팩토리를 사용하는 다른 옵션

export class A {

    id: number;

    date: Date;

    bId: number;
    readonly b: B;
}

export class B {

    id: number;
}

export class AFactory {

    constructor(
        private readonly createB: BFactory
    ) { }

    create(data: any): A {

        const createB = this.createB.create;

        return Object.assign(new A(),
            data,
            {
                get b(): B {

                    return createB({ id: data.bId });
                },
                date: new Date(data.date)
            });
    }
}

export class BFactory {

    create(data: any): B {

        return Object.assign(new B(), data);
    }
}

https://github.com/MrAntix/ts-deserialize

이런 식으로 사용

import { A, B, AFactory, BFactory } from "./deserialize";

// create a factory, simplified by DI
const aFactory = new AFactory(new BFactory());

// get an anon js object like you'd get from the http call
const data = { bId: 1, date: '2017-1-1' };

// create a real model from the anon js object
const a = aFactory.create(data);

// confirm instances e.g. dates are Dates 
console.log('a.date is instanceof Date', a.date instanceof Date);
console.log('a.b is instanceof B', a.b instanceof B);
  1. 수업을 단순하게 유지
  2. 유연성을 위해 공장에서 사출 가능

0

개인적으로 @Ingo Bürk의 옵션 # 3을 선호합니다. 그리고 복잡한 데이터 배열과 기본 데이터 배열을 지원하도록 코드를 개선했습니다.

interface IDeserializable {
  getTypes(): Object;
}

class Utility {
  static deserializeJson<T>(jsonObj: object, classType: any): T {
    let instanceObj = new classType();
    let types: IDeserializable;
    if (instanceObj && instanceObj.getTypes) {
      types = instanceObj.getTypes();
    }

    for (var prop in jsonObj) {
      if (!(prop in instanceObj)) {
        continue;
      }

      let jsonProp = jsonObj[prop];
      if (this.isObject(jsonProp)) {
        instanceObj[prop] =
          types && types[prop]
            ? this.deserializeJson(jsonProp, types[prop])
            : jsonProp;
      } else if (this.isArray(jsonProp)) {
        instanceObj[prop] = [];
        for (let index = 0; index < jsonProp.length; index++) {
          const elem = jsonProp[index];
          if (this.isObject(elem) && types && types[prop]) {
            instanceObj[prop].push(this.deserializeJson(elem, types[prop]));
          } else {
            instanceObj[prop].push(elem);
          }
        }
      } else {
        instanceObj[prop] = jsonProp;
      }
    }

    return instanceObj;
  }

  //#region ### get types ###
  /**
   * check type of value be string
   * @param {*} value
   */
  static isString(value: any) {
    return typeof value === "string" || value instanceof String;
  }

  /**
   * check type of value be array
   * @param {*} value
   */
  static isNumber(value: any) {
    return typeof value === "number" && isFinite(value);
  }

  /**
   * check type of value be array
   * @param {*} value
   */
  static isArray(value: any) {
    return value && typeof value === "object" && value.constructor === Array;
  }

  /**
   * check type of value be object
   * @param {*} value
   */
  static isObject(value: any) {
    return value && typeof value === "object" && value.constructor === Object;
  }

  /**
   * check type of value be boolean
   * @param {*} value
   */
  static isBoolean(value: any) {
    return typeof value === "boolean";
  }
  //#endregion
}

// #region ### Models ###
class Hotel implements IDeserializable {
  id: number = 0;
  name: string = "";
  address: string = "";
  city: City = new City(); // complex data
  roomTypes: Array<RoomType> = []; // array of complex data
  facilities: Array<string> = []; // array of primitive data

  // getter example
  get nameAndAddress() {
    return `${this.name} ${this.address}`;
  }

  // function example
  checkRoom() {
    return true;
  }

  // this function will be use for getting run-time type information
  getTypes() {
    return {
      city: City,
      roomTypes: RoomType
    };
  }
}

class RoomType implements IDeserializable {
  id: number = 0;
  name: string = "";
  roomPrices: Array<RoomPrice> = [];

  // getter example
  get totalPrice() {
    return this.roomPrices.map(x => x.price).reduce((a, b) => a + b, 0);
  }

  getTypes() {
    return {
      roomPrices: RoomPrice
    };
  }
}

class RoomPrice {
  price: number = 0;
  date: string = "";
}

class City {
  id: number = 0;
  name: string = "";
}
// #endregion

// #region ### test code ###
var jsonObj = {
  id: 1,
  name: "hotel1",
  address: "address1",
  city: {
    id: 1,
    name: "city1"
  },
  roomTypes: [
    {
      id: 1,
      name: "single",
      roomPrices: [
        {
          price: 1000,
          date: "2020-02-20"
        },
        {
          price: 1500,
          date: "2020-02-21"
        }
      ]
    },
    {
      id: 2,
      name: "double",
      roomPrices: [
        {
          price: 2000,
          date: "2020-02-20"
        },
        {
          price: 2500,
          date: "2020-02-21"
        }
      ]
    }
  ],
  facilities: ["facility1", "facility2"]
};

var hotelInstance = Utility.deserializeJson<Hotel>(jsonObj, Hotel);

console.log(hotelInstance.city.name);
console.log(hotelInstance.nameAndAddress); // getter
console.log(hotelInstance.checkRoom()); // function
console.log(hotelInstance.roomTypes[0].totalPrice); // getter
// #endregion

-1

당신은 아래처럼 할 수 있습니다

export interface Instance {
  id?:string;
  name?:string;
  type:string;
}

var instance: Instance = <Instance>({
      id: null,
      name: '',
      type: ''
    });

실제로 예상되는 객체 유형의 런타임 인스턴스는 생성되지 않습니다. 형식에 기본 속성 만있는 경우 작동하는 것으로 보이지만 형식에 메서드가 있으면 실패합니다. 런타임에는 인터페이스 정의를 사용할 수 없습니다 (빌드 시간 만 해당).
Todd

-1
**model.ts**
export class Item {
    private key: JSON;
    constructor(jsonItem: any) {
        this.key = jsonItem;
    }
}

**service.ts**
import { Item } from '../model/items';

export class ItemService {
    items: Item;
    constructor() {
        this.items = new Item({
            'logo': 'Logo',
            'home': 'Home',
            'about': 'About',
            'contact': 'Contact',
        });
    }
    getItems(): Item {
        return this.items;
    }
}

아래 예제와 같이 내용을 호출하십시오.
user8390810

<a class="navbar-brand" href="#"> {{keyItems.key.logo}} </a>
user8390810 2012

이것은 "필요에 따라 클래스를 인스턴스화"하는 것 같지 않습니다.
LexieHankins
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.