Як проаналізувати рядок JSON у Typescript


108

Чи є спосіб проаналізувати рядки як JSON у Typescript.
Приклад: У JS ми можемо використовувати JSON.parse(). Чи існує подібна функція в Typescript?

У мене є рядок об’єкта JSON наступним чином:

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

1
На своїй домашній сторінці він пише, що "TypeScript - це набраний набір JavaScript, який компілюється до простого JavaScript". Функція JSON.parse () повинна використовуватися як зазвичай.
sigalor

1
Я використовую текстовий редактор Atom, і коли я роблю JSON.parse, я отримую помилку: аргумент типу '{}' не можна призначити параметру типу 'рядок'
ssd20072

22
Це дуже елементарне запитання, і для когось воно може здатися тривіальним, але тим не менш це дійсне запитання, і еквівалент не може бути знайдений в SO (я не мав), тому немає жодної реальної причини, чому б не тримати питання працює, і, на мій погляд, не слід також голосувати проти.
Nitzan Tomer

2
@SanketDeshpande Коли ви використовуєте JSON.parse, в результаті ви отримуєте об'єкт, а не a string(докладніше див. Мою відповідь). Якщо ви хочете перетворити об'єкт на рядок, тоді вам потрібно використовувати його JSON.stringify.
Nitzan Tomer

2
Насправді це не просто питання з двох причин. По-перше, JSON.parse () не повертає такий самий об'єкт - він буде відповідати деякому інтерфейсу, але будь-що інтелектуальне, наприклад, Accessors, не буде присутнє. Крім того, ми, безсумнівно, хочемо, щоб SO були там, куди ходять люди, коли вони гуглить речі?
speciesНевідомо

Відповіді:


175

Typescript - це (надмножина) javascript, тому ви просто використовуєте, JSON.parseяк і в javascript:

let obj = JSON.parse(jsonString);

Тільки те, що в машинописі ви можете мати тип для отриманого об'єкта:

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

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

( код на дитячому майданчику )


9
як перевірити правильність вводу (перевірка типу, одна з цілей машинопису)? заміна вводу '{ "myString": "string", "myNumber": 4 }'на '{ "myString": "string", "myNumberBAD": 4 }'не провалиться, і obj.myNumber повернеться невизначеним.
Девід Портабелла,

3
@DavidPortabella Ви не можете перевіряти тип вмісту рядка. Це проблема виконання, і перевірка типу стосується часу компіляції
Nitzan Tomer

2
в порядку. як я можу перевірити, що машинопис obj задовольняє своєму інтерфейсу під час виконання? тобто, що myNumber не визначено у цьому прикладі. наприклад, у Scala Play ви б використовували Json.parse(text).validate[MyObj]. playframework.com/documentation/2.6.x/ScalaJson, як ви можете зробити те саме в машинописі (можливо, для цього існує зовнішня бібліотека?)?
Девід Портабелла,

1
@DavidPortabella Неможливо це зробити не так просто, оскільки під час виконання MyObjне існує. Існує безліч інших потоків в SO про цю тему, наприклад: Перевірте, чи об’єкт реалізує інтерфейс під час виконання за допомогою TypeScript
Nitzan Tomer

7
добре, дякую. щодня я більше впевнений у використанні scalajs.
Девід Портабелла,

7

Безпечний для типу 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обгортка може взяти захист типу як вхідне та повернути проаналізоване, набране значення:

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. Зовнішні бібліотеки

Запис захисних функцій типу вручну стає громіздким, якщо вам потрібно перевірити багато різних значень. Є бібліотеки, які можуть допомогти у виконанні цього завдання - приклади (без вичерпного списку):

  • io-ts: відн. популярний (на сьогодні 3,2 тис. зірок), fp-tsзалежність від однолітків, функціональний стиль програмування
  • zod: досить новий (репо: 2020-03-07), прагне бути більш процедурним / об'єктно-орієнтованим, ніжio-ts
  • typescript-is: Трансформатор TS для API компілятора, потрібна додаткова обгортка типу ttypescript
  • typescript-json-schema/ ajv: Створіть схему JSON із типів і перевіріть її за допомогоюajv

Більше інформації


4

Якщо ви хочете, щоб ваш 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під час виконання вбудовану функцію, яка приймає довільне введення і повертає astring .

Ви можете бачити, як це працює тут . Спробуйте наступні речі, щоб переконати себе:

  • Наведіть курсор на const valueвизначення, щоб побачити, що спливаюче вікно показує правильний тип.
  • Спробуйте змінити , "Bob"щоб 123і повторно запустити зразок. У вашій консолі ви побачите, що ім'я було належним чином перетворено у рядок "123".

ви дали приклад: "якби nameнасправді 123це було перетворено на "123". Це, здається, неправильно. Я valueповертаюся {name: 123..не {name:"123"..тоді, коли
скопіюю

Дивно, це працює для мене. Спробуйте тут: typecriptlang.org/play/index.html (використовуючи 123замість "Bob").
чові

Я не думаю, що вам потрібно визначити Transformedтип. Ви можете просто використовувати Object. type Descriptor<T extends Object> = { ... };
lovasoa

Дякую @lovasoa, ви праві. TransformedТип абсолютно НЕ потрібен. Відповідь я оновив відповідно.
chowey

Якщо ви дійсно хочете перевірити, що об'єкт JSON має правильні типи, ви не хочете 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, }
);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.