How to parse JSON string in Typescript

后端 未结 7 1460
[愿得一人]
[愿得一人] 2020-12-04 18:29

Is there a way to parse strings as JSON in Typescript.
Example: In JS, we can use JSON.parse(). Is there a similar function in Typescript?

I have a

相关标签:
7条回答
  • 2020-12-04 19:05

    Type-safe JSON.parse

    You can continue to use JSON.parse, as TS is a JS superset. There is still a problem left: JSON.parse returns any, which undermines type safety. Here are two options for stronger types:

    1. User-defined type guards (playground)

    Custom type guards are the simplest solution and often sufficient for external data validation:

    // 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 can then take a type guard as input and return the parsed, typed value:

    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 }
    
    Usage example:
    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 might be extended to fail fast or try/catch JSON.parse errors.

    2. External libraries

    Writing type guard functions manually becomes cumbersome, if you need to validate many different values. There are libraries to assist with this task - examples (no comprehensive list):

    • io-ts: rel. popular (3.2k stars currently), fp-ts peer dependency, functional programming style
    • zod: quite new (repo: 2020-03-07), strives to be more procedural/object-oriented than io-ts
    • typescript-is: TS transformer for compiler API, additional wrapper like ttypescript needed
    • typescript-json-schema/ajv: Create JSON schema from types and validate it with ajv

    More infos

    • Runtime type checking #1573
    • Interface type check with Typescript
    • TypeScript: validating external data
    0 讨论(0)
  • 2020-12-04 19:07

    TS has a JavaScript runtime

    Typescript has a JavaScript runtime because it gets compiled to JS. This means JS objects which are built in as part of the language such as JSON, Object, and Math are also available in TS. Therefore we can just use the JSON.parse method to parse the JSON string.

    Example:

    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"] 
    

    The only thing now is that parsedObj is type any which is generally a bad practice in TS. We can type the object if we are using type guards. Here is an example:

    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
    }
    
    0 讨论(0)
  • 2020-12-04 19:08

    Typescript is (a superset of) javascript, so you just use JSON.parse as you would in javascript:

    let obj = JSON.parse(jsonString);
    

    Only that in typescript you can have a type to the resulting object:

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

    (code in playground)

    0 讨论(0)
  • 2020-12-04 19:14

    If you want your JSON to have a validated Typescript type, you will need to do that validation work yourself. This is nothing new. In plain Javascript, you would need to do the same.

    Validation

    I like to express my validation logic as a set of "transforms". I define a Descriptor as a map of transforms:

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

    Then I can make a function that will apply these transforms to arbitrary input:

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

    Now, not only am I validating my JSON input, but I am building up a Typescript type as I go. The above generic types ensure that the result infers the types from your "transforms".

    In case the transform throws an error (which is how you would implement validation), I like to wrap it with another error showing which key caused the error.

    Usage

    In your example, I would use this as follows:

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

    Now value will be typed, since String and Boolean are both "transformers" in the sense they take input and return a typed output.

    Furthermore, the value will actually be that type. In other words, if name were actually 123, it will be transformed to "123" so that you have a valid string. This is because we used String at runtime, a built-in function that accepts arbitrary input and returns a string.

    You can see this working here. Try the following things to convince yourself:

    • Hover over the const value definition to see that the pop-over shows the correct type.
    • Try changing "Bob" to 123 and re-run the sample. In your console, you will see that the name has been properly converted to the string "123".
    0 讨论(0)
  • 2020-12-04 19:16

    There is a great library for it ts-json-object

    In your case you would need to run the following code:

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

    This library will validate the json before parsing

    0 讨论(0)
  • 2020-12-04 19:19

    JSON.parse is available in TypeScript, so you can just use it :

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

    However, you will often want to parse a JSON object while making sure it matches a certain type, rather than dealing with a value of type any. In that case, you can define a function such as the following :

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

    This function takes a JSON string and an object containing individual functions that load each field of the object you are creating. You can use it like so:

    const value = parse_json(
      '{"name": "Bob", "error": false}',
      { name: String, error: Boolean, }
    );
    
    0 讨论(0)
提交回复
热议问题