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}
ssd20072
źródło
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 )

Nitzan Tomer
źródło
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

ford04
źródło
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".
chowey
źródło
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.

jfu
źródło
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

Elizeusz Sterngold
źródło
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, }
);
lovasoa
źródło