Jak analizować ciąg JSON w skrypcie Typescript


109

Czy istnieje sposób na analizowanie ciągów jako JSON w Typescript.
Przykład: W JS możemy użyć JSON.parse(). Czy w Typescript jest podobna funkcja?

Mam ciąg obiektu JSON w następujący sposób:

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

1
Na swojej stronie głównej jest napisane, że „TypeScript to nadzbiór typu JavaScript, który kompiluje się do zwykłego JavaScript”. Funkcja JSON.parse () powinna być użyteczna jak zwykle.
— sigalor

1
Używam edytora tekstu Atom i kiedy robię JSON.parse, pojawia się błąd: Argumentu typu „{}” nie można przypisać do parametru typu „string”
— ssd20072

23
To jest bardzo podstawowe pytanie i niektórym może się wydawać trywialne, ale mimo wszystko jest to ważne pytanie, a odpowiednika nie można znaleźć w SO (nie mam), więc nie ma prawdziwego powodu, dla którego nie miałbym tego pytania bieganie i moim zdaniem nie powinno być również głosowane w dół.
— Nitzan Tomer

2
@SanketDeshpande Kiedy używasz JSON.parse, otrzymujesz obiekt jako wynik, a nie string(zobacz moją odpowiedź po więcej). Jeśli chcesz zamienić obiekt w ciąg, musisz użyć JSON.stringifyzamiast tego.
— Nitzan Tomer

2
Właściwie nie jest to proste pytanie z dwóch powodów. Po pierwsze, JSON.parse () nie zwraca tego samego rodzaju obiektu - będzie pasować do części interfejsu, ale niczego inteligentnego, na przykład akcesorów, nie będzie. Co więcej, na pewno chcemy, aby SO był tam, gdzie ludzie szukają rzeczy w Google?
— speciesUnknown

Odpowiedzi:


182

Maszynopis to (nadzbiór) javascript, więc używasz go tak, JSON.parsejak w javascript:

let obj = JSON.parse(jsonString);

Tyle tylko, że w maszynopisie możesz mieć typ do wynikowego obiektu:

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

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

( kod na placu zabaw )


10
jak sprawdzić, czy dane wejściowe są prawidłowe (sprawdzanie typu, jeden z celów maszynopisu)? zastąpienie wejścia '{ "myString": "string", "myNumber": 4 }'przez '{ "myString": "string", "myNumberBAD": 4 }'nie zakończy się niepowodzeniem, a obj.myNumber zwróci wartość undefined.
— David Portabella

3
@DavidPortabella Nie można sprawdzać typu w zawartości ciągu. Jest to problem z uruchomieniem, a sprawdzanie typów dotyczy czasu kompilacji
— Nitzan Tomer

2
ok. jak mogę sprawdzić, czy maszynopis obj spełnia wymagania swojego interfejsu w czasie wykonywania? to znaczy, że myNumber nie jest niezdefiniowana w tym przykładzie. na przykład w Scala Play użyłbyś Json.parse(text).validate[MyObj]. playframework.com/documentation/2.6.x/ScalaJson jak możesz zrobić to samo w maszynie (może jest do tego zewnętrzna biblioteka?)?
— David Portabella

1
@DavidPortabella Nie da się tego zrobić, nie jest to łatwe, ponieważ w czasie wykonywania MyObjnie istnieje. W SO jest wiele innych wątków na ten temat, na przykład: Sprawdź, czy obiekt implementuje interfejs w czasie wykonywania z TypeScript
— Nitzan Tomer

7
ok dzięki. codziennie jestem bardziej przekonany o używaniu scalajs.
— David Portabella

8

Bezpieczne dla typów JSON.parse

Możesz nadal używać JSON.parse, ponieważ TS to nadzbiór JS. Nadal pozostaje problem: JSON.parsezwraca any, co podważa bezpieczeństwo typów. Oto dwie opcje dla silniejszych typów:

1. Osłony typu zdefiniowanego przez użytkownika ( plac zabaw )

Zabezpieczenia typu niestandardowego są najprostszym rozwiązaniem i często wystarczają do weryfikacji danych zewnętrznych:

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

Opakowanie JSON.parsemoże następnie przyjąć typ ochrony jako dane wejściowe i zwrócić przeanalizowaną, wpisaną wartość:

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 }
Przykład użycia:
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`
}

safeJsonParsemoże zostać przedłużony, aby szybko zawieść lub spróbować / złapać JSON.parsebłędy.

2. Biblioteki zewnętrzne

Ręczne pisanie funkcji ochrony typów staje się uciążliwe, jeśli trzeba zweryfikować wiele różnych wartości. Istnieją biblioteki, które mogą pomóc w tym zadaniu - przykłady (brak pełnej listy):

Więcej informacji


4

Jeśli chcesz, aby twój JSON miał zweryfikowany typ skryptu, musisz sam wykonać tę walidację. To nic nowego. W zwykłym JavaScript musisz zrobić to samo.

Uprawomocnienie

Lubię wyrażać swoją logikę walidacji jako zbiór „transformacji”. Definiuję a Descriptorjako mapę przekształceń:

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

Następnie mogę utworzyć funkcję, która zastosuje te przekształcenia do dowolnego wejścia:

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

Teraz nie tylko sprawdzam poprawność danych wejściowych JSON, ale buduję typ Typescript na bieżąco. Powyższe typy ogólne zapewniają, że wynik wywnioskuje typy z Twoich „przekształceń”.

W przypadku, gdy transformacja zgłasza błąd (tak jak zaimplementowałbyś walidację), chciałbym owinąć go innym błędem pokazującym, który klucz spowodował błąd.

Stosowanie

W twoim przykładzie użyłbym tego w następujący sposób:

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

Teraz valuezostaną wpisane, ponieważ Stringi Booleanto zarówno „Transformers” w sensie podejmowania wejście i powrót wpisywanych wyjście.

Co więcej, valuetestament faktycznie będzie tego typu. Innymi słowy, jeśli namefaktycznie tak było 123, zostanie przekształcone w "123", aby uzyskać prawidłowy ciąg. Dzieje się tak, ponieważ Stringw czasie wykonywania używaliśmy wbudowanej funkcji, która akceptuje dowolne dane wejściowe i zwraca plik string.

Możesz zobaczyć, jak to działa tutaj . Spróbuj następujących rzeczy, aby się przekonać:

  • Najedź kursorem na const valuedefinicję, aby zobaczyć, że wyskakujące okienko pokazuje prawidłowy typ.
  • Spróbuj zmienić "Bob"się 123i ponownie uruchomić próbki. W konsoli zobaczysz, że nazwa została poprawnie przekonwertowana na ciąg "123".

podałeś przykład, „jeśli namefaktycznie był 123, zostanie przekształcony na "123". Wydaje się, że jest to niepoprawne. Mój valuewraca {name: 123..nie, {name:"123"..kiedy
— kopiuję, wklejam

Dziwne, na mnie to działa. Wypróbuj tutaj: typescriptlang.org/play/index.html (używając 123zamiast "Bob").
— chowey

Nie sądzę, żebyś musiał definiować Transformedtyp. Możesz po prostu użyć Object. type Descriptor<T extends Object> = { ... };
— lovasoa

Dzięki @lovasoa, masz rację. TransformedTyp jest całkowicie zbędne. Odpowiednio zaktualizowałem odpowiedź.
— chowey

Jeśli faktycznie chcesz sprawdzić, czy obiekt JSON ma poprawne typy, nie chcesz, 123aby był on automatycznie konwertowany na ciąg "123", ponieważ jest to liczba w obiekcie JSON.
— xuiqzy

1

Możesz dodatkowo użyć bibliotek, które wykonują sprawdzanie poprawności typu json, takich jak Sparkson . Pozwalają zdefiniować klasę TypeScript, do której chcesz przeanalizować swoją odpowiedź, w twoim przypadku może to być:

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

Biblioteka sprawdzi, czy wymagane pola znajdują się w ładunku JSON i czy ich typy są poprawne. Może również przeprowadzić szereg weryfikacji i konwersji.


1
Należy wspomnieć, że jesteś głównym współautorem powyższej biblioteki.
— ford04

1

Jest do tego świetna biblioteka ts-json-object

W twoim przypadku musisz uruchomić następujący kod:

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

Ta biblioteka sprawdzi poprawność pliku json przed analizowaniem


0

JSON.parse jest dostępny w języku TypeScript, więc możesz go po prostu użyć:

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

Jednak często będziesz chciał przeanalizować obiekt JSON, upewniając się, że pasuje do określonego typu, zamiast zajmować się wartością typu any. W takim przypadku możesz zdefiniować funkcję, taką jak:

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

Ta funkcja przyjmuje ciąg JSON i obiekt zawierający indywidualne funkcje, które ładują każde pole tworzonego obiektu. Możesz go używać w ten sposób:

const value = parse_json(
  '{"name": "Bob", "error": false}',
  { name: String, error: Boolean, }
);
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.