วิธีแยกวิเคราะห์สตริง JSON ใน typescript


122

มีวิธีแยกวิเคราะห์สตริงเป็น JSON ใน typescript หรือไม่
ตัวอย่าง: ใน JS เราสามารถใช้JSON.parse()ไฟล์. มีฟังก์ชันที่คล้ายกันใน typescript หรือไม่?

ฉันมีสตริงออบเจ็กต์ JSON ดังนี้:

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

1
ในหน้าแรกระบุว่า "TypeScript คือส่วนเหนือของ JavaScript แบบพิมพ์ที่รวบรวมเป็น JavaScript ธรรมดา" ฟังก์ชัน JSON.parse () ควรใช้งานได้เหมือนปกติ
sigalor

1
ฉันใช้โปรแกรมแก้ไขข้อความ Atom และเมื่อฉันทำ JSON.parse ฉันได้รับข้อผิดพลาด: อาร์กิวเมนต์ประเภท '{}' ไม่สามารถกำหนดให้กับพารามิเตอร์ประเภท 'สตริง'
ssd20072

26
นี่เป็นคำถามพื้นฐานมากและอาจดูไม่สำคัญสำหรับบางคน แต่ก็เป็นคำถามที่ถูกต้องไม่มีเลยแม้แต่น้อยและไม่พบสิ่งที่เทียบเท่าใน SO (ฉันยังไม่เคย) ดังนั้นจึงไม่มีเหตุผลที่แท้จริงว่าทำไมไม่เก็บคำถามไว้ ทำงานและในความคิดของฉันก็ไม่ควรลงคะแนนเช่นกัน
Nitzan Tomer

3
@SanketDeshpande เมื่อคุณใช้JSON.parseคุณจะได้รับวัตถุเป็นผลลัพธ์ไม่ใช่ a string(ดูคำตอบของฉันสำหรับข้อมูลเพิ่มเติม) หากคุณต้องการเปลี่ยนวัตถุให้เป็นสตริงคุณต้องใช้JSON.stringifyแทน
Nitzan Tomer

3
จริงๆแล้วมันไม่ใช่คำถามง่ายๆด้วย 2 เหตุผล ประการแรก JSON.parse () ไม่ส่งคืนอ็อบเจ็กต์ประเภทเดียวกัน - มันจะจับคู่อินเทอร์เฟซบางอย่าง แต่สิ่งที่ชาญฉลาดเช่น accessors จะไม่ปรากฏ นอกจากนี้แน่นอนว่าเราต้องการให้ SO เป็นที่ที่ผู้คนไปเมื่อพวกเขาใช้ Google?
speciesUnknown

คำตอบ:


194

typescript คือ (superset of) javascript ดังนั้นคุณจึงใช้JSON.parseตามที่คุณต้องการในจาวาสคริปต์:

let obj = JSON.parse(jsonString);

เฉพาะใน typescript คุณสามารถมีประเภทให้กับวัตถุผลลัพธ์:

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

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

( รหัสในสนามเด็กเล่น )


11
วิธีตรวจสอบว่าอินพุตถูกต้อง (การตรวจสอบประเภทหนึ่งในวัตถุประสงค์ของ typescript) การแทนที่อินพุต'{ "myString": "string", "myNumber": 4 }'โดย'{ "myString": "string", "myNumberBAD": 4 }'จะไม่ล้มเหลวและ obj.myNumber จะส่งกลับไม่ได้กำหนด
David Portabella

3
@DavidPortabella คุณไม่สามารถตรวจสอบประเภทเนื้อหาของสตริงได้ มันเป็นปัญหารันไทม์และการตรวจสอบประเภทเป็นเวลารวบรวม
Nitzan Tomer

3
ตกลง. ฉันจะตรวจสอบได้อย่างไรว่า typescript obj ตรงตามอินเตอร์เฟสที่รันไทม์ นั่นคือ myNumber ไม่ได้ระบุไว้ในตัวอย่างนี้ ตัวอย่างเช่นใน Scala Play Json.parse(text).validate[MyObj]คุณจะใช้ playframework.com/documentation/2.6.x/ScalaJsonคุณจะทำสิ่งเดียวกันใน typescript ได้อย่างไร (อาจมีไลบรารีภายนอกให้ทำเช่นนั้น)?
David Portabella

1
@DavidPortabella ไม่มีทางที่จะทำเช่นนั้นไม่ใช่ง่ายๆเพราะMyObjไม่มีรันไทม์ มีเธรดอื่น ๆ มากมายใน SO เกี่ยวกับเรื่องนี้ตัวอย่างเช่นตรวจสอบว่าอ็อบเจ็กต์ใช้อินเทอร์เฟซที่รันไทม์ด้วย TypeScript หรือไม่
Nitzan Tomer

8
โอเคขอบคุณ. ทุกๆวันฉันมั่นใจมากขึ้นเกี่ยวกับการใช้สเกลาจ์
David Portabella

10

ประเภทปลอดภัย JSON.parse

คุณสามารถใช้งานต่อไปได้JSON.parseเนื่องจาก TS เป็นซูเปอร์เซ็ต JS ยังคงมีปัญหาที่เหลืออยู่: การJSON.parseส่งคืนanyซึ่งทำลายความปลอดภัยของประเภท มีสองตัวเลือกสำหรับประเภทที่แข็งแกร่งกว่า:

1. ผู้ใช้กำหนดประเภทยาม ( สนามเด็กเล่น )

ที่กำหนดเองประเภทยามเป็นทางออกที่ง่ายและมักจะเพียงพอสำหรับการตรวจสอบข้อมูลภายนอก:

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

JSON.parseเสื้อคลุมแล้วสามารถใช้เป็นชนิดยามเป็น input และคืนแยกวิเคราะห์ค่าพิมพ์:

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

type ParseResult<T> =
  | { parsed: T; hasError: false; error?: undefined }
  | { parsed?: undefined; hasError: true; error?: unknown }
ตัวอย่างการใช้งาน:
const json = '{ "name": "Foo", "description": "Bar" }';
const result = safeJsonParse(isMyType)(json) // result: ParseResult<MyType>
if (result.hasError) {
  console.log("error :/")  // further error handling here
} else {
  console.log(result.parsed.description) // result.parsed now has type `MyType`
}

safeJsonParseอาจถูกขยายให้ล้มเหลวอย่างรวดเร็วหรือลอง / จับJSON.parseข้อผิดพลาด

2. ห้องสมุดภายนอก

การเขียนฟังก์ชัน type guard ด้วยตนเองจะยุ่งยากหากคุณต้องการตรวจสอบค่าต่างๆ มีไลบรารีที่ช่วยในงานนี้ - ตัวอย่าง (ไม่มีรายการที่ครอบคลุม):

  • io-ts: rel. เป็นที่นิยม (3.2k ดาวในปัจจุบัน), fp-tsการพึ่งพาเพียร์, รูปแบบการเขียนโปรแกรมเชิงฟังก์ชัน
  • zod: ค่อนข้างใหม่ (repo: 2020-03-07) มุ่งมั่นที่จะเป็นขั้นตอน / เชิงวัตถุมากกว่าio-ts
  • typescript-is: TS transformer สำหรับ compiler API จำเป็นต้องใช้wrapper เพิ่มเติมเช่นttypescript
  • typescript-json-schema/ ajv: สร้างสคีมา JSON จากประเภทและตรวจสอบความถูกต้องด้วยajv

ข้อมูลเพิ่มเติม


6

หากคุณต้องการให้ JSON ของคุณมีประเภท typescript ที่ตรวจสอบได้คุณจะต้องดำเนินการตรวจสอบความถูกต้องด้วยตัวเอง นี่ไม่ใช่เรื่องใหม่ ใน Javascript ธรรมดาคุณจะต้องทำเช่นเดียวกัน

การตรวจสอบ

ฉันชอบแสดงตรรกะการตรวจสอบของฉันเป็นชุดของ "การแปลง" ฉันกำหนดDescriptorเป็นแผนที่ของการแปลง:

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

จากนั้นฉันสามารถสร้างฟังก์ชันที่จะใช้การแปลงเหล่านี้กับอินพุตโดยพลการ:

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

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

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

การใช้งาน

ในตัวอย่างของคุณฉันจะใช้สิ่งนี้ดังนี้:

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

ตอนนี้valueจะถูกพิมพ์เนื่องจากStringและBooleanเป็น "หม้อแปลง" ทั้งคู่ในแง่ที่รับอินพุตและส่งคืนเอาต์พุตที่พิมพ์

นอกจากนี้valueจะเป็นประเภทนั้นจริงๆ กล่าวอีกนัยหนึ่งคือถ้าnameเป็นจริง123มันจะถูกเปลี่ยนเป็นเพื่อ"123"ให้คุณมีสตริงที่ถูกต้อง เนื่องจากเราใช้Stringที่รันไทม์ซึ่งเป็นฟังก์ชันในตัวที่รับอินพุตโดยพลการและส่งกลับไฟล์string.

คุณสามารถดูนี้ทำงานที่นี่ ลองทำสิ่งต่อไปนี้เพื่อโน้มน้าวตัวเอง:

  • วางเมาส์เหนือconst valueคำจำกัดความเพื่อดูว่าป๊อปโอเวอร์แสดงประเภทที่ถูกต้อง
  • ลองเปลี่ยน"Bob"ไป123อีกครั้งเรียกใช้ตัวอย่าง "123"ในคอนโซลของคุณคุณจะเห็นว่าชื่อที่ได้รับการแปลงอย่างถูกต้องเพื่อสตริง

คุณยกตัวอย่าง "ถ้าnameเป็นจริง123มันจะเปลี่ยนเป็น"123"สิ่งนี้ดูเหมือนจะไม่ถูกต้องของฉันvalueกำลังจะกลับมา{name: 123..ไม่ใช่{name:"123"..ตอนที่ฉันคัดลอกวางโค้ดทั้งหมดของคุณทั้งหมดและทำการเปลี่ยนแปลงนั้น
จอนคอม

แปลกมันเหมาะกับฉัน ลองใช้ที่นี่: typescriptlang.org/play/index.html (ใช้123แทน"Bob")
chowey

ฉันไม่คิดว่าคุณต้องกำหนดTransformedประเภท คุณสามารถใช้Object. type Descriptor<T extends Object> = { ... };
lovasoa

ขอบคุณ @lovasoa คุณถูกต้อง Transformedชนิดที่ไม่จำเป็นทั้งหมด ฉันได้อัปเดตคำตอบตามนั้น
chowey

1
หากคุณต้องการตรวจสอบว่า JSON Object มีประเภทที่ถูกต้องคุณคงไม่ต้องการ123แปลงเป็นสตริงโดยอัตโนมัติ"123"เนื่องจากเป็นตัวเลขในออบเจ็กต์ JSON
xuiqzy

1

นอกจากนี้คุณยังสามารถใช้ห้องสมุดที่ดำเนินการตรวจสอบประเภทของ JSON ของคุณเช่นSparkson ช่วยให้คุณกำหนดคลาส TypeScript ซึ่งคุณต้องการแยกวิเคราะห์คำตอบของคุณในกรณีของคุณอาจเป็น:

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

ไลบรารีจะตรวจสอบว่าฟิลด์ที่ต้องการมีอยู่ในเพย์โหลด JSON หรือไม่และประเภทถูกต้องหรือไม่ นอกจากนี้ยังสามารถทำการตรวจสอบความถูกต้องและการแปลงได้อีกด้วย


1
คุณควรพูดถึงว่าคุณเป็นผู้สนับสนุนหลักของห้องสมุดข้างต้น
ford04

1

มีห้องสมุดที่ยอดเยี่ยมสำหรับมันts-json-object

ในกรณีของคุณคุณจะต้องเรียกใช้รหัสต่อไปนี้:

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

ไลบรารีนี้จะตรวจสอบความถูกต้องของ json ก่อนที่จะแยกวิเคราะห์


0

JSON.parse มีอยู่ใน TypeScript ดังนั้นคุณสามารถใช้งานได้:

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

แต่คุณมักจะต้องการที่จะแยกวัตถุ JSON anyในขณะที่การทำให้แน่ใจว่ามันตรงกับบางประเภทมากกว่าการจัดการกับค่าของชนิด ในกรณีนั้นคุณสามารถกำหนดฟังก์ชันดังต่อไปนี้:

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

ฟังก์ชันนี้ใช้สตริง JSON และอ็อบเจ็กต์ที่มีฟังก์ชันแต่ละฟังก์ชันที่โหลดแต่ละฟิลด์ของอ็อบเจ็กต์ที่คุณกำลังสร้าง คุณสามารถใช้มันดังนี้:

const value = parse_json(
  '{"name": "Bob", "error": false}',
  { name: String, error: Boolean, }
);

0

TS มีรันไทม์ JavaScript

typescript มีรันไทม์ JavaScript เนื่องจากคอมไพล์เป็น JS วิธีนี้ JS วัตถุที่มีการสร้างขึ้นในฐานะเป็นส่วนหนึ่งของภาษาเช่นJSON, ObjectและMathยังมีใน TS ดังนั้นเราสามารถใช้ไฟล์JSON.parseวิธีการแยกวิเคราะห์สตริง JSON

ตัวอย่าง:

const JSONStr = '{"name": "Bob", "error": false}'

// The JSON object is part of the runtime
const parsedObj = JSON.parse(JSONStr);

console.log(parsedObj);
// [LOG]: {
//   "name": "Bob",
//   "error": false
// } 

// The Object object is also part of the runtime so we can use it in TS
const objKeys = Object.keys(parsedObj);

console.log(objKeys);
// [LOG]: ["name", "error"] 

สิ่งเดียวในตอนนี้คือ parsedObj เป็นประเภทanyซึ่งโดยทั่วไปเป็นการปฏิบัติที่ไม่ดีใน TS เราสามารถพิมพ์วัตถุได้หากเราใช้ type guards นี่คือตัวอย่าง:

const JSONStr = '{"name": "Bob", "error": false}'
const parsedObj = JSON.parse(JSONStr);

interface nameErr {
  name: string;
  error: boolean;
}

function isNameErr(arg: any): arg is nameErr {
  if (typeof arg.name === 'string' && typeof arg.error === 'boolean') {
    return true;
  } else {
    return false;
  }
}

if (isNameErr(parsedObj)) {
  // Within this if statement parsedObj is type nameErr;
  parsedObj
}

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