7

Hãy bắt đầu với một cảm ơn trước :)Mapping thứ bậc JSON để nguyên cảo-KnockoutJS gõ Object

OK, Vì vậy, tôi đang cố gắng để tải/bản đồ thứ bậc nguyên cảo/KnockoutJS lớp gõ từ phù hợp với dữ liệu JSON sử dụng loại trực tiếp. ánh xạ plugin, hệ thống phân cấp có thể ở mức độ thứ N.

Tôi biết tôi có thể thực hiện các thao tác sau để ánh xạ/tải lớp cấp cao nhất từ ​​dữ liệu JSON.

var qry = ko.mapping.fromJS(jsData, {}, new Query()); 

Tuy nhiên, tôi không thể hiểu được cách ánh xạ/tải dữ liệu JSON thứ bậc, phức tạp vào một tập hợp các lớp TypeScript/KnockoutJS và xây dựng mối quan hệ cha/con.

Tôi đã đọc vô số bản nghệ thuật, nhưng tất cả đều thiếu hụt khi nói đến các mối quan hệ phân cấp vượt ra ngoài các ví dụ đơn giản về cha/con và tôi không thể tìm thấy gì bằng plugin knockout.mapping.

Dưới đây là các định nghĩa cắt giảm của tôi về các lớp TypeScript mà tôi muốn ánh xạ/tải. Tôi là một nhà phát triển C++/C#, do đó, JavaScript về bản chất này rất mới đối với tôi.

nguyên cảo Objects

Các JSON sẽ giống như thế này:

{ 
    "ID": 2, 
    "Name": "Northwind 2", 
    "RootTargetID": 2, 
    "RootTarget": { 
     "ID": 2, 
     "Name": "Customers", 
     "ParentID": null, 
     "FilterID": 2, 
     "Queries": [], 
     "Children": [], 
     "Parent": null, 
     "Selects": [ 
      { 
       "ID": 3, 
       "Name": "CompanyName", 
       "Aggregation": "None", 
       "TargetID": 2, 
       "Target": null 
      }, 
      { 
       "ID": 4, 
       "Name": "ContactName", 
       "Aggregation": "None", 
       "TargetID": 2, 
       "Target": null 
      } 
     ], 
     "Filter": { 
      "FilterClauseID": 2, 
      "Type": "AND", 
      "Left": null, 
      "Right": null, 
      "ParentID": null, 
      "QueryTargets": [], 
      "Parent": null, 
      "Children": [ 
       { 
        "FilterClauseID": 3, 
        "Type": "NE", 
        "Left": "Country", 
        "Right": "Germany", 
        "ParentID": 2, 
        "QueryTargets": [], 
        "Parent": null, 
        "Children": [] 
       }, 
       { 
        "FilterClauseID": 4, 
        "Type": "NE", 
        "Left": "Country", 
        "Right": "Mexico", 
        "ParentID": 2, 
        "QueryTargets": [], 
        "Parent": null, 
        "Children": [] 
       } 
      ] 
     } 
    } 
} 
+0

giải pháp được thêm vào bên dưới – DIGGIDY

Trả lời

6

OK, vì vậy tôi thêm một chút xuống dòng bây giờ, sau rất nhiều tóc kéo và kiểm tra numerious . Dưới đây là một ví dụ gần như làm việc của những gì tôi đang cố gắng để đạt được, vấn đề duy nhất với điều này là nó không có vẻ để bản đồ một cách chính xác, mặc dù bước qua mã dường như cho thấy nó được tải một cách chính xác. Chỉ khi tôi sử dụng nó với các ràng buộc của tôi nó ném một ràng buộc không bị ràng buộc null vào RootTaget.Filter.Type, mà phải được điền với một giá trị.

Tôi vẫn đang cố gắng tìm ra lý do tại sao, nhưng tôi sẽ hoan nghênh các đề xuất về những gì có thể xảy ra. :)

VỚI DOANH NGHIỆP CỐ ĐỊNH VÀ LÀM VIỆC

bán làm việc nguyên cảo

///<reference path="Scripts/typings/jquery/jquery.d.ts"/> 
///<reference path="Scripts/typings/knockout/knockout.d.ts"/> 
///<reference path="Scripts/typings/knockout.mapping/knockout.mapping.d.ts"/> 

module ViewModel 
{ 
    export class Query { 
     public ID: KnockoutObservable<number>; 
     public Name: KnockoutObservable<string>; 
     public RootTargetID: KnockoutObservable<number>; 
     public RootTarget: KnockoutObservable<QueryTarget>; 

     constructor(json: any) { 
      this.ID = ko.observable<number>(0); 
      this.Name = ko.observable<string>(); 
      this.RootTargetID = ko.observable<number>(); 
      this.RootTarget = ko.observable<QueryTarget>(); 

      var mapping = { 
       'RootTarget': { 
        create: function (args) { 
         return new QueryTarget(args.data, null); 
        } 
       } 
      }; 

      ko.mapping.fromJS(json, mapping, this); 

     } 
    } 

    export class QueryTarget { 
     public ID: KnockoutObservable<number>; 
     public Name: KnockoutObservable<string>; 
     public ParentID: KnockoutObservable<number>; 
     public Children: KnockoutObservableArray<QueryTarget>; 
     public Parent: KnockoutObservable<QueryTarget>; 
     public Selects: KnockoutObservableArray<QuerySelect>; 
     public FilterID: KnockoutObservable<number>; 
     public Filter: KnockoutObservable<FilterClause>; 

     constructor(json: any, parent: QueryTarget) { 
      this.ID = ko.observable<number>(0); 
      this.Name = ko.observable<string>(); 
      this.ParentID = ko.observable<number>(0); 
      this.Children = ko.observableArray<QueryTarget>(); 
      this.Parent = ko.observable<QueryTarget>(parent); 
      this.Selects = ko.observableArray<QuerySelect>(); 
      this.FilterID = ko.observable<number>(0); 
      this.Filter = ko.observable<FilterClause>(); 

      var mapping = { 
       'Children': { 
        create: function (args) { 
         return new QueryTarget(args.data, this); 
        } 
       }, 
       'Selects': { 
        create: function (args) { 
         return new QuerySelect(args.data, this); 
        } 
       }, 
       'Filter': { 
        create: function (args) { 
         return new FilterClause(args.data, null); 
        } 
       } 
      }; 

      ko.mapping.fromJS(json, mapping, this); 
     } 
    } 

    export class QuerySelect { 
     public ID: KnockoutObservable<number>; 
     public Name: KnockoutObservable<string>; 
     public Aggregation: KnockoutObservable<string>; 
     public TargetID: KnockoutObservable<number>; 
     public Target: KnockoutObservable<QueryTarget>; 

     constructor(json: any, parent: QueryTarget) { 
      this.ID = ko.observable<number>(); 
      this.Name = ko.observable<string>(); 
      this.Aggregation = ko.observable<string>(); 
      this.TargetID = ko.observable<number>(); 
      this.Target = ko.observable<QueryTarget>(parent); 

      ko.mapping.fromJS(json, {}, this); 
     } 
    } 

    export class FilterClause { 
     public FilterClauseID: KnockoutObservable<number>; 
     public Type: KnockoutObservable<string>; 
     public Left: KnockoutObservable<string>; 
     public Right: KnockoutObservable<string>; 
     public ParentID: KnockoutObservable<number>; 
     public Parent: KnockoutObservable<FilterClause>; 
     public Children: KnockoutObservableArray<FilterClause>; 

     constructor(json: any, parent: FilterClause) { 
      this.FilterClauseID = ko.observable<number>(); 
      this.Type = ko.observable<string>(); 
      this.Left = ko.observable<string>(); 
      this.Right = ko.observable<string>(); 
      this.ParentID = ko.observable<number>(); 
      this.Parent = ko.observable<FilterClause>(parent); 
      this.Children = ko.observableArray<FilterClause>(); 

      var mapping = { 
       'Children': { 
        create: function (args) { 
         return new FilterClause(args.data, this); 
        } 
       } 
      }; 

      ko.mapping.fromJS(json, mapping, this); 
     } 
    } 

    export class QueryModuleViewModel 
    { 
     public QueryObj: Query; 

     constructor() { 

      var json = { 
       "ID": 2, 
       "Name": "Northwind 2", 
       "RootTargetID": 2, 
       "RootTarget": { 
        "ID": 2, 
        "Name": "Customers", 
        "ParentID": null, 
        "FilterID": 2, 
        "Queries": [], 
        "Children": [], 
        "Parent": null, 
        "Selects": [ 
         { 
          "ID": 3, 
          "Name": "CompanyName", 
          "Aggregation": "None", 
          "TargetID": 2, 
          "Target": null 
         }, 
         { 
          "ID": 4, 
          "Name": "ContactName", 
          "Aggregation": "None", 
          "TargetID": 2, 
          "Target": null 
         } 
        ], 
        "Filter": { 
         "FilterClauseID": 2, 
         "Type": "AND", 
         "Left": null, 
         "Right": null, 
         "ParentID": null, 
         "QueryTargets": [], 
         "Parent": null, 
         "Children": [ 
          { 
           "FilterClauseID": 3, 
           "Type": "NE", 
           "Left": "Country", 
           "Right": "Germany", 
           "ParentID": 2, 
           "QueryTargets": [], 
           "Parent": null, 
           "Children": [] 
          }, 
          { 
           "FilterClauseID": 4, 
           "Type": "NE", 
           "Left": "Country", 
           "Right": "Mexico", 
           "ParentID": 2, 
           "QueryTargets": [], 
           "Parent": null, 
           "Children": [] 
          } 
         ] 
        } 
       } 
      } 

      //$.getJSON("/api/query/2", null, 
      // d => { 
      //  this.QueryObj = new Query(d); 
      // }) 

      this.QueryObj = new Query(json); 
     } 
    } 
} 

window.onload =() => { 
    ko.applyBindings(new ViewModel.QueryModuleViewModel()); 
}; 

html ràng buộc kiểm tra

<!DOCTYPE html> 
<html lang="en"> 
<head> 
    <meta charset="utf-8" /> 
    <title>TypeScript Knockout Mapping Query Test</title> 
    <link rel="stylesheet" href="app.css" type="text/css" /> 

    <script src="Scripts/jquery-2.0.2.js" type="text/javascript"></script> 
    <script src="Scripts/knockout-2.2.1.debug.js" type="text/javascript"></script> 
    <script src="Scripts/knockout.mapping-latest.debug.js" type="text/javascript"></script> 
    <script src="query.js"></script> 
    <!--<script src="my_js_query_test_all.js"></script>--> 

</head> 
<body> 
    <h1>TypeScript Knockout Mapping Query Test</h1> 
    <div data-bind="with: QueryObj"> 
     <span data-bind="blah: console.log($context)"></span> 

     <p>Query Name: <input data-bind="value: Name" /></p> 

     <hr /> 
     <p>Quick test of RootTarget and Filter data</p> 
     <p>RootTarget.ID: <input data-bind="value: RootTarget().ID" /></p> 
     <p>RootTarget.Name: <input data-bind="value: RootTarget().Name" /></p> 

     <p>TYPE: <input data-bind="value: RootTarget().Filter().Type" /></p> 

     <hr /> 
     <p>RootTarget.FilterClause Hierarcy</p> 
     <div data-bind="with: RootTarget().Filter"> 
      <div data-bind="template: { name: 'QueryListClauseTemplate' }"></div> 
     </div> 

     <hr /> 
     <p>RootTarget.Selects</p> 
     <div data-bind="foreach: { data: RootTarget().Selects }"> 
      <div data-bind="template: { name: 'QueryListSelectsTemplate' }"></div> 
     </div> 

    </div> 

    <script type="text/template" id="QueryListClauseTemplate"> 

     <a title="FilterClause.Type" href="#" data-bind="text: Type" /> 

     <div data-bind="foreach: { data: Children }"> 
      <div data-bind="template: { name: 'QueryListClauseTemplate' }"></div> 
     </div> 
    </script> 

    <script type="text/template" id="QueryListSelectsTemplate"> 
     <a title="Select.Name" href="#" data-bind="text: Name" /> 
    </script> 

</body> 
</html> 
+0

thêm mã kiểm tra html – DIGGIDY

+0

Đã khắc phục một vài lỗi nhỏ nhưng vẫn không hoạt động như mong đợi. – DIGGIDY

+0

Dường như không hiểu được câu hỏi này, tôi đoán TypeScript có thể làm phức tạp hơn vấn đề gốc của câu hỏi. Tôi đã hỏi một câu hỏi khác với nguyên nhân gốc, [SEE LINK] (http://stackoverflow.com/questions/17612550/mapping-json-with-knockout-fails-to-populate-type-defined-object-properties) Tôi sẽ cập nhật cái này với một giải pháp nhẹ nhàng khi tôi nhận được câu trả lời. – DIGGIDY

1

cách tiếp cận khác là để tạo một tệp .d.ts định nghĩa các giao diện TypeScript mô tả các bộ sưu tập lồng nhau của các kiểu quan sát được tạo ra bởi plugin bản đồ loại bỏ cho các lớp C# của bạn.

Sau đó, bạn sẽ nhận được loại kiểm tra mà bạn mong muốn bằng cách sử dụng tệp .d.ts (giống như cách bạn sử dụng tệp .d.ts từ dự án github được nhập chắc chắn để kiểm tra loại cho các thư viện javaScript hiện có).

Tôi đã tạo ứng dụng bảng điều khiển để kiểm tra C# dll của tôi bằng cách sử dụng phản chiếu. Tôi đã sử dụng một thuộc tính tùy chỉnh để đánh dấu các kiểu mà các giao diện TypeScript được tạo ra.(Tôi cũng phải tạo một thuộc tính tùy chỉnh để đánh dấu các thuộc tính nào KHÔNG được tạo ra như là quan sát được, vì plugin ánh xạ chỉ làm cho các nút lá của các bộ sưu tập lồng nhau của bạn là các quan sát được).

Điều này làm việc tốt cho tôi vì tôi đã có thể tạo lại tệp .d.ts nhanh chóng khi mô hình C# của tôi thay đổi. Và tôi đã có thể kiểm tra loại cho tất cả các phần của ViewModel loại trực tiếp của tôi.

//the custom attributes to use on your classes 
    public class GenerateTypeScript : Attribute 
    { 
     public override string ToString() 
     { 
      return "TypeScriptKnockout.GenerateTypeScript"; 
     } 
    } 

    public class NotObservable : Attribute 
    { 
     public override string ToString() 
     { 
      return "TypeScriptKnockout.NotObservable"; 
     } 
    } 


    //example of using the attributes 
    namespace JF.Models.Dtos 
    { 
     [TypeScriptKnockout.GenerateTypeScript] 
     public class ForeclosureDetails : IValidatableObject, IQtipErrorBindable 
     { 
      [TypeScriptKnockout.NotObservable] 
      public Foreclosure Foreclosure { get; set; } 

      //strings used for form input and validation 
      public string SaleDateInput { get; set; } 
      public string SaleTimeInput { get; set; }  
      ....etc. 



    //the console app to generate the .d.ts interfaces 
    void Main() 
    { 
     string dllPath = @"binFolder"; 
     string dllFileName = "JF.dll"; 
     Assembly assembly = Assembly.LoadFrom(Path.Combine(dllPath,dllFileName)); 
     List<string> interfacesToIgnore = new List<string>{"IValidatableObject"}; //stuff that won't exist on the client-side, Microsoft Interfaces 

     var types = from t in assembly.GetTypes() 
       where (t.IsClass || t.IsInterface) 
       && t.GetCustomAttributes(true).Any(a => ((Attribute)a).ToString() == "TypeScriptKnockout.GenerateTypeScript") 
       orderby t.IsClass, t.Name 
       select t; 

     Console.WriteLine("/// <reference path=\"..\\Scripts\\typings\\knockout\\knockout.d.ts\" />"); 

     foreach (var t in types) 
     { 

      //type 
      Console.Write("{0} {1}", " interface", t.Name); 

      //base class 
      if(t.BaseType != null && t.BaseType.Name != "Object"){ 
       Console.Write(" extends {0}", t.BaseType.Name); 
      }  

      //interfaces 
      var interfacesImplemented = t.GetInterfaces().Where (i => !interfacesToIgnore.Contains(i.Name)).ToList(); 
      if(interfacesImplemented.Count() > 0){ 
       Console.Write(" extends"); 
       var icounter = 0; 
       foreach (var i in interfacesImplemented) 
       { 
        if(icounter > 0) 
         Console.Write(","); 
        Console.Write(" {0}", i.Name); 
        icounter++; 
       } 
      } 
      Console.WriteLine(" {"); 

      //properties 
      foreach (var p in t.GetProperties()) 
      { 
       var NotObservable = p.GetCustomAttributes(true).Any(pa => ((Attribute)pa).ToString() == "TypeScriptKnockout.NotObservable"); 
       Console.WriteLine("  {0}: {1};", p.Name, GetKnockoutType(p, NotObservable)); 
      } 
      Console.WriteLine(" }\n");   

     } 
    } 


    public string GetKnockoutType(PropertyInfo p, bool NotObservable){ 

     if(p.PropertyType.Name.StartsWith("ICollection") 
     || p.PropertyType.Name.StartsWith("IEnumerable") 
     || p.PropertyType.Name.StartsWith("Dictionary") 
     || p.PropertyType.Name.StartsWith("List")) 
     {  
      return String.Format("KnockoutObservableArray<{0}>", p.PropertyType.GenericTypeArguments[0].Name); 
     } 
     var typeName = p.PropertyType.Name; 
     if(typeName.StartsWith("Nullable")) 
      typeName = p.PropertyType.GenericTypeArguments[0].Name; 


     switch (typeName) 
     { 
      case "Int32" : 
      case "Decimal" : 
       return NotObservable ? "number" : "KnockoutObservable<number>"; 

      case "String" : 
       return NotObservable ? "string" : "KnockoutObservable<string>"; 

      case "DateTime" :  
       return NotObservable ? "Date" : "KnockoutObservable<Date>"; 

      case "Boolean": 
       return NotObservable ? "boolean" : "KnockoutObservable<boolean>"; 

      case "Byte[]": 
       return NotObservable ? "any" : String.Format("KnockoutObservableAny; //{0}", typeName); 

      default: 
       if(NotObservable) 
        return typeName; 

       bool isObservableObject = true; 
       var subProperties = p.PropertyType.GetProperties(); 
       foreach (var subProp in subProperties) 
       { 
        if(
         subProp.PropertyType.IsClass 
         && !subProp.PropertyType.Name.StartsWith("String") 
         && !subProp.PropertyType.Name.StartsWith("ICollection") 
         && !subProp.PropertyType.Name.StartsWith("IEnumerable") 
         && !subProp.PropertyType.Name.StartsWith("Dictionary") 
         && !subProp.PropertyType.Name.StartsWith("List")    
        ) 
        { 
         isObservableObject = false; 
        }    
       } 

       return isObservableObject ? String.Format("KnockoutObservable<{0}>", typeName) : typeName;        
     } 
    } 

    //example of the interfaces generated 

    interface ForeclosureDetails extends IQtipErrorBindable { 
     Foreclosure: Foreclosure; 
     SaleDateInput: KnockoutObservable<string>; 
     SaleTimeInput: KnockoutObservable<string>; 
     ...etc.