Cách phân tích cú pháp chuỗi JSON trong Typecript


98

Có cách nào để phân tích cú pháp chuỗi dưới dạng JSON trong Typecript không.
Ví dụ: Trong JS, chúng ta có thể sử dụng JSON.parse(). Có một chức năng tương tự trong Typecript không?

Tôi có một chuỗi đối tượng JSON như sau:

{"name": "Bob", "error": false}

1
Trên trang chủ của nó, nó nói rằng "TypeScript là một tập hợp JavaScript được đánh máy sẽ biên dịch thành JavaScript thuần túy". Hàm JSON.parse () có thể được sử dụng như bình thường.
ký tên,

1
Tôi đang sử dụng soạn thảo văn bản Atom và khi tôi làm một JSON.parse, tôi nhận được lỗi: Đối số của loại '{}' là không thể chuyển nhượng để tham số có kiểu 'string'
ssd20072

21
Đây là một câu hỏi rất cơ bản, và nó có vẻ tầm thường đối với một số người nhưng nó là một câu hỏi hợp lệ không hơn không kém và không thể tìm thấy một câu hỏi tương đương trong SO (tôi chưa có) nên không có lý do thực sự nào để không tiếp tục câu hỏi đang chạy, và theo quan điểm của tôi cũng không nên bỏ phiếu.
Nitzan Tomer,

2
@SanketDeshpande Khi bạn sử dụng, JSON.parsebạn nhận được một đối tượng là kết quả chứ không phải là một string(xem câu trả lời của tôi để biết thêm). Nếu bạn muốn biến một đối tượng thành một chuỗi thì bạn cần sử dụng JSON.stringifythay thế.
Nitzan Tomer,

2
Thực ra nó không phải là một câu hỏi đơn giản vì 2 lý do. Thứ nhất, JSON.parse () không trả về cùng một loại đối tượng - nó sẽ khớp với một số giao diện nhưng bất kỳ thứ gì thông minh, chẳng hạn như trình truy cập, sẽ không có mặt. Hơn nữa, chắc chắn chúng tôi muốn SO trở thành nơi mọi người tìm đến khi họ google nội dung?
gburton

Câu trả lời:


164

Typecript là (một tập hợp siêu của) javascript, vì vậy bạn chỉ cần sử dụng JSON.parsenhư trong javascript:

let obj = JSON.parse(jsonString);

Chỉ có điều trong bảng chữ cái, bạn mới có thể có kiểu cho đối tượng kết quả:

interface MyObj {
    myString: string;
    myNumber: number;
}

let obj: MyObj = JSON.parse('{ "myString": "string", "myNumber": 4 }');
console.log(obj.myString);
console.log(obj.myNumber);

( mã trong sân chơi )


9
làm thế nào để xác nhận rằng đầu vào là hợp lệ (kiểm tra kiểu, một trong những mục đích của bảng chữ)? thay thế đầu vào '{ "myString": "string", "myNumber": 4 }'bằng '{ "myString": "string", "myNumberBAD": 4 }'sẽ không thất bại và obj.myNumber sẽ trả về không xác định.
David Portabella

2
@DavidPortabella Bạn không thể kiểm tra kiểu đối với nội dung của một chuỗi. Đó là một vấn đề thời gian chạy, và kiểm tra kiểu là cho thời gian biên dịch
Nitzan Tomer

2
đồng ý. làm thế nào tôi có thể xác nhận rằng một đối tượng sắp chữ đáp ứng giao diện của nó trong thời gian chạy? nghĩa là, myNumber đó không phải là không xác định trong ví dụ này. ví dụ, trong Scala Play, bạn sẽ sử dụng Json.parse(text).validate[MyObj]. playframework.com/documentation/2.6.x/ScalaJson làm thế nào bạn có thể làm điều tương tự trong bản ghi (có thể có một thư viện bên ngoài để làm như vậy?)?
David Portabella

1
@DavidPortabella Không có cách nào để làm điều đó, không dễ dàng, vì trong thời gian chạy MyObjkhông tồn tại. Có rất nhiều chủ đề khác trong SO về chủ đề này, ví dụ: Kiểm tra xem một đối tượng cụ một giao diện khi chạy với nguyên cảo
Nitzan Tomer

7
được rồi cảm ơn. hàng ngày, tôi tin tưởng hơn về việc sử dụng scalajs.
David Portabella

4

Nếu bạn muốn JSON của mình có kiểu Typecript đã được xác thực, bạn sẽ cần phải tự mình thực hiện công việc xác nhận đó. Điều này không có gì mới. Trong Javascript đơn giản, bạn sẽ cần phải làm như vậy.

Thẩm định

Tôi thích thể hiện logic xác thực của mình như một tập hợp các "phép biến đổi". Tôi định nghĩa a Descriptorlà một bản đồ của các phép biến đổi:

type Descriptor<T> = {
  [P in keyof T]: (v: any) => T[P];
};

Sau đó, tôi có thể tạo một hàm sẽ áp dụng các biến đổi này cho đầu vào tùy ý:

function pick<T>(v: any, d: Descriptor<T>): T {
  const ret: any = {};
  for (let key in d) {
    try {
      const val = d[key](v[key]);
      if (typeof val !== "undefined") {
        ret[key] = val;
      }
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      throw new Error(`could not pick ${key}: ${msg}`);
    }
  }
  return ret;
}

Bây giờ, tôi không chỉ xác thực đầu vào JSON của mình mà còn đang xây dựng kiểu Typecript khi tôi tiếp tục. Các kiểu chung chung ở trên đảm bảo rằng kết quả suy ra các kiểu từ "các phép biến đổi" của bạn.

Trong trường hợp biến đổi gây ra lỗi (đó là cách bạn sẽ triển khai xác thực), tôi muốn kết hợp nó với một lỗi khác hiển thị khóa nào đã gây ra lỗi.

Sử dụng

Trong ví dụ của bạn, tôi sẽ sử dụng điều này như sau:

const value = pick(JSON.parse('{"name": "Bob", "error": false}'), {
  name: String,
  error: Boolean,
});

Bây giờ valuesẽ được nhập, vì StringBooleancả hai đều là "máy biến áp" theo nghĩa chúng nhận đầu vào và trả về đầu ra đã nhập.

Hơn nữa, ý valuemuốn thực sự là loại đó. Nói cách khác, nếu namethực sự là 123, nó sẽ được chuyển đổi để "123"bạn có một chuỗi hợp lệ. Điều này là do chúng tôi đã sử dụng Stringtrong thời gian chạy, một hàm tích hợp sẵn chấp nhận đầu vào tùy ý và trả về a string.

Bạn có thể thấy điều này hoạt động ở đây . Hãy thử những điều sau để thuyết phục bản thân:

  • Di chuột qua const valueđịnh nghĩa để xem cửa sổ bật lên hiển thị đúng loại.
  • Hãy thử thay đổi "Bob"để 123và chạy lại mẫu. Trong bảng điều khiển của bạn, bạn sẽ thấy rằng tên đã được chuyển đổi đúng thành chuỗi "123".

bạn đã đưa ra một ví dụ, "nếu namethực sự là 123, nó sẽ được chuyển đổi thành "123". Điều này có vẻ không chính xác. Của tôi valuesẽ {name: 123..không quay lại {name:"123"..khi tôi sao chép, dán chính xác tất cả mã của bạn và thực hiện thay đổi đó.
Joncom

Kỳ lạ, nó phù hợp với tôi. Hãy thử nó tại đây: stylescriptlang.org/play/index.html (sử dụng 123thay vì "Bob").
chowey

Tôi không nghĩ rằng bạn cần phải xác định một Transformedloại. Bạn chỉ có thể sử dụng Object. type Descriptor<T extends Object> = { ... };
lovasoa

Cảm ơn @lovasoa, bạn đã chính xác. Các Transformedloại là hoàn toàn không cần thiết. Tôi đã cập nhật câu trả lời cho phù hợp.
chowey

3

Loại an toàn JSON.parse

Bạn có thể tiếp tục sử dụng JSON.parse, vì TS là một tập hợp JS. Vẫn còn một vấn đề:JSON.parse trả về any, làm suy yếu độ an toàn của kiểu. Dưới đây là hai tùy chọn cho các loại mạnh hơn:

1. Loại bảo vệ do người dùng xác định ( sân chơi )

Gõ bảo vệ là giải pháp đơn giản nhất và thường đủ để xác thực dữ liệu bên ngoài:

// For example, you expect to parse a given value with `MyType` shape
type MyType = { name: string; description: string; }

// Validate this value with a custom type guard
function isMyType(o: any): o is MyType {
  return "name" in o && "description" in o
}

A JSON.parse wrapper sau đó mất một người bảo vệ kiểu như là đầu vào và trả về phân tích cú pháp, giá trị nhập:

type ParseResult<T> =
  | { parsed: T; hasError: false; error?: undefined }
  | { parsed?: undefined; hasError: true; error?: unknown }

const safeJsonParse = <T>(guard: (o: any) => o is T) => (text: string): ParseResult<T> => {
  const parsed = JSON.parse(text)
  return guard(parsed) ? { parsed, hasError: false } : { hasError: true }
}
Ví dụ sử dụng:
const json = "{ \"name\": \"Foo\", \"description\": \"Bar\" }"
const result = safeJsonParse(isMyType)(json) // result: ParseResult<MyType>
if (result.hasError) {
  console.log("error :/")  // error handling here
} else {
  console.log(result.parsed.description) // parsed has type `MyType`
}

safeJsonParsecó thể được mở rộng để thất bại nhanh chóng hoặc thử / bắt JSON.parselỗi.

2. Thư viện bên ngoài

Việc viết các hàm bảo vệ kiểu thủ công trở nên cồng kềnh, nếu bạn cần xác nhận nhiều giá trị khác nhau. Có khá nhiều thư viện hỗ trợ công việc này - ví dụ (không có danh sách đầy đủ):

Thông tin khác


1

Ngoài ra, bạn có thể sử dụng các thư viện thực hiện xác thực kiểu json của mình, chẳng hạn như Sparkson . Chúng cho phép bạn xác định một lớp TypeScript, mà bạn muốn phân tích cú pháp phản hồi của mình, trong trường hợp của bạn, nó có thể là:

import { Field } from "sparkson";
class Response {
   constructor(
      @Field("name") public name: string,
      @Field("error") public error: boolean
   ) {}
}

Thư viện sẽ xác thực nếu các trường bắt buộc có trong tải trọng JSON và nếu loại của chúng chính xác. Nó cũng có thể thực hiện một loạt các xác nhận và chuyển đổi.


Bạn nên đề cập rằng bạn là người đóng góp chính cho thư viện trên.
ford04

1

Có một thư viện tuyệt vời cho nó ts-json-object

Trong trường hợp của bạn, bạn sẽ cần chạy mã sau:

import {JSONObject, required} from 'ts-json-object'

class Response extends JSONObject {
    @required
    name: string;

    @required
    error: boolean;
}

let resp = new Response({"name": "Bob", "error": false});

Thư viện này sẽ xác thực json trước khi phân tích cú pháp


0

JSON.parse có sẵn trong TypeScript, vì vậy bạn chỉ cần sử dụng nó:

JSON.parse('{"name": "Bob", "error": false}') // Returns a value of type 'any'

Tuy nhiên, bạn thường muốn phân tích cú pháp một đối tượng JSON trong khi đảm bảo rằng nó khớp với một kiểu nhất định, thay vì xử lý một giá trị của kiểu any. Trong trường hợp đó, bạn có thể xác định một hàm như sau:

function parse_json<TargetType extends Object>(
  json: string,
  type_definitions: { [Key in keyof TargetType]: (raw_value: any) => TargetType[Key] }
): TargetType {
  const raw = JSON.parse(json); 
  const result: any = {};
  for (const key in type_definitions) result[key] = type_definitions[key](raw[key]);
  return result;
}

Hàm này nhận một chuỗi JSON và một đối tượng chứa các hàm riêng lẻ tải từng trường của đối tượng bạn đang tạo. Bạn có thể sử dụng nó như vậy:

const value = parse_json(
  '{"name": "Bob", "error": false}',
  { name: String, error: Boolean, }
);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.