ฉันจะเริ่มต้นวัตถุ TypeScript ด้วยวัตถุ JSON ได้อย่างไร


199

ฉันได้รับวัตถุ JSON จากการโทร AJAX ไปยังเซิร์ฟเวอร์ REST วัตถุนี้มีชื่อคุณสมบัติที่ตรงกับคลาสของฉัน TypeScript (นี่คือการติดตามคำถามนี้ )

วิธีที่ดีที่สุดในการเริ่มต้นมันคืออะไร? ฉันไม่คิดว่ามันจะใช้งานได้เพราะคลาส (& วัตถุ JSON) มีสมาชิกที่เป็นรายการของวัตถุและสมาชิกที่เป็นคลาสและคลาสเหล่านั้นมีสมาชิกที่เป็นรายการและ / หรือชั้นเรียน

แต่ฉันต้องการวิธีที่ค้นหาชื่อสมาชิกและมอบหมายให้พวกเขาสร้างรายการและคลาสอินสแตนซ์ตามต้องการดังนั้นฉันไม่ต้องเขียนรหัสที่ชัดเจนสำหรับสมาชิกทุกคนในทุกชั้นเรียน (มีจำนวนมาก!)


1
เหตุใดคุณจึงถามอีกครั้ง (ตามคำตอบที่ฉันให้ไว้ในคำถามอื่น ๆ กล่าวว่าสิ่งนี้ใช้ไม่ได้และมันเกี่ยวกับการคัดลอกคุณสมบัติไปยังวัตถุที่มีอยู่)
WiredPrairie


3
@WiredPrairie คำถามนี้จะแตกต่างกันมันจะถามว่าฉันสามารถเดินคุณสมบัติหนึ่งโดยหนึ่งและมอบหมายให้พวกเขาข้าม คำถามอื่น ๆ ที่ถามว่าฉันสามารถโยนมันได้หรือไม่
David Thielen

1
@WiredPrairie cont: หากคุณยังคงดำน้ำในคุณสมบัติจนกว่าคุณจะได้รับเพียงประเภทดั้งเดิมจากนั้นก็สามารถกำหนดได้
David Thielen

2
มันยังคงคัดลอกค่าทั้งหมดตามที่ฉันแนะนำคุณต้องทำ ไม่มีวิธีการใหม่ในการทำสิ่งนี้ใน TypeScript เนื่องจากมันเป็นการออกแบบพื้นฐานของ JavaScript สำหรับวัตถุขนาดใหญ่คุณอาจไม่ต้องการคัดลอกค่าใด ๆ และเพียงแค่ "ดำเนินการ" โครงสร้างข้อมูลแทน
WiredPrairie

คำตอบ:


189

นี่เป็นช็อตสั้น ๆ เพื่อแสดงวิธีที่ต่างกันเล็กน้อย พวกเขาไม่ได้ "สมบูรณ์" และเป็นข้อจำกัดความรับผิดชอบฉันไม่คิดว่าเป็นความคิดที่ดีที่จะทำเช่นนี้ รหัสก็ไม่สะอาดเหมือนกันเพราะฉันเพิ่งพิมพ์มันด้วยกันอย่างรวดเร็ว

ยังเป็นหมายเหตุ: แน่นอนชั้นเรียน deserializable ต้องมีการก่อสร้างเริ่มต้นเป็นกรณีในภาษาอื่น ๆ ทั้งหมดที่ฉันตระหนักถึง deserialization ทุกชนิด แน่นอนว่า 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: verbose แต่วิธีเรียบร้อย

อัพเดท 2016/01/03:ในฐานะที่เป็น @GameAlchemist ชี้ให้เห็นในส่วนความเห็น ( ความคิด , การดำเนินงาน ) ณ typescript 1.7 วิธีการแก้ปัญหาที่อธิบายด้านล่างสามารถเขียนได้ในทางที่ดีใช้ตกแต่งชั้น / คุณสมบัติ

การทำให้เป็นอันดับเป็นปัญหาเสมอและในความคิดของฉันวิธีที่ดีที่สุดคือวิธีที่ไม่สั้น จากตัวเลือกทั้งหมดนี่คือสิ่งที่ฉันต้องการเพราะผู้เขียนชั้นเรียนมีการควบคุมอย่างเต็มที่กับสถานะของวัตถุ deserialized ถ้าฉันต้องเดาฉันจะบอกว่าตัวเลือกอื่น ๆ ไม่ช้าก็เร็วจะทำให้คุณเดือดร้อน (ยกเว้นว่าจาวาสคริปต์ใช้วิธีจัดการกับสิ่งนี้)

จริงๆแล้วตัวอย่างต่อไปนี้ไม่ได้ทำเพื่อความยืดหยุ่น มันแค่คัดลอกโครงสร้างของคลาสจริงๆ อย่างไรก็ตามความแตกต่างที่คุณต้องจำไว้ที่นี่คือว่าคลาสนั้นมีการควบคุมเต็มรูปแบบเพื่อใช้ 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 จัดให้มีการตกแต่งคลาส / คุณสมบัติที่ช่วยให้การเขียนวิธีที่ 4 ในทางที่เหนือกว่า
GameAlchemist

1
เอกสารที่ดีที่สุดที่ฉันพบเป็นคำตอบที่ StackOverflow: stackoverflow.com/a/29837695/856501 ฉันใช้นักตกแต่งในโครงการของฉันและถึงแม้ว่าฉันต้องการคุณสมบัติอื่น ๆ บางอย่างฉันต้องบอกว่าพวกเขาทำงานเหมือนมีเสน่ห์
GameAlchemist

2
ฉันจะไม่โดดนักตกแต่งสำหรับโครงการผลิต แต่จำไว้ว่าพวกเขายังคงเป็นคุณลักษณะทดลอง ฉันจะไม่ยึดรหัสโลกแห่งความเป็นจริงใน "การทดลอง" เพราะเท่าที่เรากังวลพวกเขาอาจจะหายไปในเวอร์ชั่นถัดไปและคุณต้องเขียนรหัสจำนวนมากหรือติดอยู่กับเวอร์ชัน TS เก่าตลอดไป แค่ $ .02 ของฉัน
RVP

35

คุณสามารถใช้Object.assignฉันไม่ทราบว่าเมื่อสิ่งนี้ถูกเพิ่มฉันกำลังใช้ typescript 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. ฟีดข้อมูลเมตาดาต้านี้ไปยัง initializer แบบเรียกซ้ำ (deserializer)

 

ประเภทข้อมูลการบันทึก

ด้วยการรวมกันของ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

มันง่ายมาก แต่ทรงพลัง มันรองรับ:

  • การทำให้เป็นลำดับและการดีซีเรียลไลเซชันของต้นไม้ทั้งวัตถุ
  • คุณสมบัติถาวรและชั่วคราวบนวัตถุเดียวกัน
  • hooks เพื่อกำหนดตรรกะการทำให้เป็นอนุกรม (de)
  • มันสามารถ (de) ทำให้เป็นอนุกรมในอินสแตนซ์ที่มีอยู่ (เหมาะสำหรับ Angular) หรือสร้างอินสแตนซ์ใหม่
  • เป็นต้น

ตัวอย่าง:

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

ฉันได้สร้างเครื่องมือที่สร้างอินเทอร์เฟซ TypeScript และรันไทม์ "แผนที่ชนิด" สำหรับการดำเนินการพิมพ์ดีดแบบรันไทม์กับผลลัพธ์ของ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 Schema เป็นขั้นตอนแรกที่ดี
LexieHankins

3

ตัวเลือก # 5: การใช้ตัวสร้าง typescript และ jQuery.extend

นี่น่าจะเป็นวิธีการบำรุงรักษามากที่สุด: เพิ่มตัวสร้างที่ใช้เป็นพารามิเตอร์โครงสร้าง json และขยายวัตถุ json ด้วยวิธีนี้คุณสามารถวิเคราะห์โครงสร้าง json ลงในโมเดลแอ็พพลิเคชันทั้งหมด

ไม่จำเป็นต้องสร้างอินเตอร์เฟสหรือแสดงรายการคุณสมบัติใน Constructor

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 callback ที่คุณได้รับ บริษัท เพื่อคำนวณเงินเดือน:

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 head ก็พอแล้ว
whale_steward

ใช่ฉันอัปเดตคำตอบเพื่อแทนที่ $ ด้วย jQuery นำเข้า jQuery.js ในส่วน html และติดตั้งและเพิ่ม @ types / jquery ใน package.json ของคุณส่วน devDependencies
Anthony Brenelière

1
โปรดทราบว่าใน Javascript คุณควรทำObject.assignซึ่งจะลบการอ้างอิงนี้ไปยัง jQuery
Léon Pelletier

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 หรือ Variant บางตัวที่ให้ Object) และไม่ต้องใช้ไลบรารีหรือมัณฑนากรภายนอก


1

ตัวเลือกที่ 4 ที่อธิบายไว้ข้างต้นเป็นวิธีที่ง่ายและดีในการทำซึ่งจะต้องรวมกับตัวเลือกที่ 2 ในกรณีที่คุณต้องจัดการลำดับชั้นของคลาสเช่นรายการสมาชิกซึ่งเป็นส่วนหนึ่งของคลาสย่อยของ สมาชิกระดับสูงพิเศษเช่นกรรมการขยายสมาชิกหรือขยายสมาชิก ในกรณีนั้นคุณต้องกำหนดประเภทของคลาสย่อยในรูปแบบ json



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 ที่ทำให้เป็นอนุกรม
ทอดด์

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

ฉันชอบตัวเลือก # 3 ของ @Ingo Bürk และฉันได้ปรับปรุงรหัสของเขาเพื่อสนับสนุนอาร์เรย์ของข้อมูลที่ซับซ้อนและอาร์เรย์ของข้อมูลดั้งเดิม

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: ''
    });

สิ่งนี้จะไม่สร้างอินสแตนซ์รันไทม์ของวัตถุชนิดที่คุณคาดหวัง มันจะปรากฏขึ้นเมื่อประเภทของคุณมีคุณสมบัติดั้งเดิมเท่านั้น แต่จะล้มเหลวเมื่อประเภทมีวิธีการ นิยามอินเตอร์เฟสยังไม่พร้อมใช้งานขณะรันไทม์ (เวลาสร้างเท่านั้น)
ทอดด์

-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;
    }
}

เรียกเนื้อหาดังตัวอย่างด้านล่าง:
8390810

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

ดูเหมือนจะไม่เป็น "[อินสแตนซ์] คลาสตามต้องการ"
LexieHankins
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.