當 c# 后端回傳一個字典物件時,我試圖找到一種處理 TypeScript 中鍵:值對的最佳方法,但我嘗試過的任何方法都沒有按預期作業。
這是我的 C# 代碼:
var displayFileds = new Dictionary<string, string>();
displayFileds.Add("A", "A Value");
displayFileds.Add("B", "B Value");
這是打字稿
type MyResponse = {
displayFields: Map<string,string>
}
console.log(response.displayFields)
//sample of Map created in Typescript directly
let map = new Map([
["A", "A value"],
["B", "B value"]
]);
console.log(map)
我希望看到 console.log 的值相同,但它們完全不同,但為什么呢?如果無法將物件直接反序列化為 Map,那么在 TypeScript 中處理 Dictionaries 的最佳方法是什么?

[ 編輯 ]
這是從后端獲取日期的方式(我正在使用 Axios):
const { data } = await secureAxios.post<MyResponse>
(ApiEndpoints.GetResponse, request)
和回應型別:
export type MyResponse= {
displayFields: Map<string,string>
}
正如您從上面的 devtools 輸出中看到的那樣,我沒有像型別建議的那樣取回 Map<string,string> 而是只是一個物件,這不是我想要的
uj5u.com熱心網友回復:
- 你的打字稿
type MyResponse是錯誤的:默認情況下,兩者
Newtonsoft.Json(又名 JSON.NET)和System.Text.Json將序列化為Dictionary<String,String>具有字串鍵和字串值的 JSON 物件。- IE
interface MyResponse { readonly [key: string]: string }
- IE
JSON只能包含物件常量(即JavaScript的一些組合物
object和Array值(使用表達{}和[]分別地),和其他原始文本(嚴格限于string文字,number文本,和true/false/null。- 即您不能呼叫 JavaScript 類建構式 - 也不能使用任何需要構造物件的物件/型別,因此
Map在 JSON 中參考該型別是非法的。
- 即您不能呼叫 JavaScript 類建構式 - 也不能使用任何需要構造物件的物件/型別,因此
但是您的 TypeScript
type MyResponse使用了該Map物件,如上所述,該物件不正確/錯誤/非法。
可能的解決方案:
如果字典鍵集是“靜態的”并且構成您的 Web 服務“合同”的一部分,那么您應該使用定義良好的 C# DTO
class并讓您的 JSON 序列化程式為您處理它。這比擁有任意和未記錄的 JSON 物件鍵要好得多,這意味著您可以使用代碼生成工具為任何平臺(Web、TypeScript、Java、Rust,甚至COBOL)自動創建 Web 服務客戶端庫。
MyResponse.cs
public class MyResponse { [JsonConstructor] public MyResponse( [JsonProperty("displayFields")] MyResponseFields displayFields ) { this.DisplayFields = displayFields ?? throw new ArgumentNullException(nameof(displayFields)); } [JsonProperty("displayFields")] public MyResponseFields DisplayFields { get; } } public class MyResponseFields { [JsonConstructor] public MyResponseFields( [JsonProperty("a")] String a, [JsonProperty("b")] String b ) { this.A = a; this.B = b; } [JsonProperty("a")] public String A { get; } [JsonProperty("a")] public String B { get; } }MyResponse.ts
interface MyResponseFields { readonly a: string; readonly a: string; } interface MyResponse { readonly displayFields: MyResponseFields; }
如果您始終
displayFields是動態的并且無法使用任何型別的靜態介面表示(即您確實想發送帶有鍵和值的任意 JSON ),則使用如下所示的內容:objectstringstringMyResponse.cs
public class MyResponse { [JsonConstructor] public MyResponse( [JsonProperty("displayFields")] IReadOnlyDictionary<String,String> displayFields ) { this.DisplayFields = displayFields ?? throw new ArgumentNullException(nameof(displayFields)); } [JsonProperty("displayFields")] public IReadOnlyDictionary<String,String> DisplayFields { get; } }MyResponse.ts
interface ReadonlyStringDictionaryObject { readonly [key: string]: string; } interface MyResponse { readonly displayFields: ReadonlyStringDictionaryObject; }并在 TypeScript 中像這樣使用(假設您正在使用
fetch):async function getMyResponse(): MyResponse { const resp = fetch( '/my/service/endpoint' ); if( resp.status === 200 && resp.headers.get('Content-Type')?.startsWith('application/json') ) { const json = await resp.json(); if( isMyResponse( json ) ) { return json; } } throw new Error( "something went wrong" ); } function isMyResponse( obj: unknown ): obj is MyResponse { return ( typeof obj === 'object' && obj !== null && 'displayFields' in obj && ( typeof ( obj as any ).displayFields === 'object' ); ); } function convertMyResponseToMap( r: MyResponse ): Map<string,string> { return new Map( Object.entries( r.displayFields ) ); } async function doStuff() { const myResponse = await getMyResponse(); const asMap = convertMyResponseToMap( myResponse ); console.log( asMap ); }還...
- 在 JSON 中,物件屬性(又名鍵)應該始終是
pascalCase,而不是TitleCase。 - 僅使用 TypeScript
interface型別來描述 JSON 回應。避免使用,type因為這樣更容易無意中使用非 JSON 安全的技術和型別。 - Also, TypeScript interfaces should describe immutable objects: all properties should be
readonly, all collections should bereadonly T[]and so on.- This is a good idea because it prevents things breaking in cases where you have two or more separate consumers of the same response object - where those consumers process the same response object-graph instance sequentially, and you don't want one altering the received response object such that it would break the other consumer.
- TypeScript's type-system is not entirely "sound", and data-mutations are an easy way to encounter unsound situations. Keeping things immutable makes everything else easier.
- This is a good idea because it prevents things breaking in cases where you have two or more separate consumers of the same response object - where those consumers process the same response object-graph instance sequentially, and you don't want one altering the received response object such that it would break the other consumer.
- 這也擴展到 C#:注意 DTO
class定義如何都是不可變的,使用建構式通過引數驗證來初始化自己,并且所有屬性都是只讀的。- 在
[JsonConstructor]和[JsonProperty],如果你打算也由C#中反序列化JSON屬性在類的建構式時,才需要。 - 如果您想知道為什么有這么多重復(與更簡單的可變C# DTO 類相比,請責怪 C# 語言設計者讓自定義建構式如此乏味——但是請考慮使用新的 C#
record型別,因為這會減少很多乏味,但會限制您在建構式中執行驗證邏輯的能力。
- 在
- 在 JSON 中,物件屬性(又名鍵)應該始終是
uj5u.com熱心網友回復:
Axios使用方便的通用設計模式。您可以提供回應的型別,但它不會在運行時強制執行 - 在型別安全方面它等同于型別斷言。
您的 C# 后端將 Dictionary 序列化為以下 JSON 字串:
{
"A":"A Value",
"B":"B Value"
}
由 Axios 決議為 JS 物件:
{
A: "A Value",
B: "B Value"
}
這正是您從回應中得到的記錄。
更一般地說,這意味著:
- 如果您信任后端以正確的格式接收資料,則應指定回應的型別
- 一個常見的陷阱是將類指定為回應型別 - 這不會像
JSON.parse回傳普通物件那樣作業,而無需建立原型鏈。
您現在有 2 個選擇:
按原樣使用回傳的選項,指定正確的型別
正確的型別(匹配從 JSON.parse 實際回傳的資料)是索引型別:
type DisplayFields = {
[key: string]: string
}
這是慣用的 TS,將使您能夠通過鍵訪問值。
將結果轉換為 Map
如果您依賴于Map在其余代碼中使用 a:
- 對 Axios 請求使用索引型別
- 收到回應后立即將其轉換為 Map。
注意:我不確定后端序列化的資料的形狀 - 如果它只是 Map 或包含 displayFields 屬性下的 Map 的物件。根據接收資料的形狀修改前端的回傳型別。
uj5u.com熱心網友回復:
您可以response.displayFields通過簡單地使用 Javascript 物件實作與 相同的輸出:
const obj = {
A: "A value",
B: "B value"
}
和 typescript 型別定義一樣可以寫成:
const obj: Record<string, string> = {
A: "A value",
B: "B value"
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/399039.html
