Existe uma maneira de analisar strings como JSON em Typescript.
Exemplo: Em JS, podemos usar JSON.parse()
. Existe uma função semelhante em Typescript?
Eu tenho uma string de objeto JSON da seguinte maneira:
{"name": "Bob", "error": false}
Existe uma maneira de analisar strings como JSON em Typescript.
Exemplo: Em JS, podemos usar JSON.parse()
. Existe uma função semelhante em Typescript?
Eu tenho uma string de objeto JSON da seguinte maneira:
{"name": "Bob", "error": false}
JSON.parse
obtém um objeto como resultado e não um string
(veja minha resposta para mais informações). Se você quiser transformar um objeto em uma string, você precisa usar JSON.stringify
.
Respostas:
Texto tipográfico é (um superconjunto de) javascript, então você apenas usa JSON.parse
como faria em javascript:
let obj = JSON.parse(jsonString);
Apenas que no texto digitado você pode ter um tipo para o objeto resultante:
interface MyObj {
myString: string;
myNumber: number;
}
let obj: MyObj = JSON.parse('{ "myString": "string", "myNumber": 4 }');
console.log(obj.myString);
console.log(obj.myNumber);
'{ "myString": "string", "myNumber": 4 }'
por '{ "myString": "string", "myNumberBAD": 4 }'
não falhará e obj.myNumber retornará indefinido.
Json.parse(text).validate[MyObj]
. playframework.com/documentation/2.6.x/ScalaJson como você pode fazer o mesmo no texto datilografado (talvez haja uma biblioteca externa para fazer isso?)?
MyObj
não existe. Existem muitos outros tópicos no SO sobre este assunto, por exemplo: Verifique se um objeto implementa uma interface em tempo de execução com TypeScript
JSON.parse
Você pode continuar a usar JSON.parse
, já que TS é um superconjunto JS. Resta um problema: JSON.parse
devoluções any
, o que prejudica a segurança de tipo. Aqui estão duas opções para tipos mais fortes:
Protetores de tipo personalizado são a solução mais simples e muitas vezes suficientes para validação de dados externos:
// 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
}
UMA JSON.parse
wrapper pode então pegar um protetor de tipo como entrada e retornar o valor digitado analisado:
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 }
Exemplo de uso:
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
pode ser estendido para falhar rapidamente ou JSON.parse
erros de tentativa / captura .
Escrever funções de proteção de tipo manualmente torna-se complicado, se você precisar validar muitos valores diferentes. Existem bibliotecas para auxiliar nesta tarefa - exemplos (nenhuma lista abrangente):
io-ts
: rel. popular (3,2 mil estrelas atualmente), fp-ts
dependência de pares, estilo de programação funcionalzod
: bastante novo (repo: 2020-03-07), se esforça para ser mais procedimental / orientado a objetos do queio-ts
typescript-is
: Transformador TS para API do compilador, wrapper adicional como ttypescript necessáriotypescript-json-schema
/ ajv
: Criar esquema JSON a partir de tipos e validá-lo comajv
Se você deseja que seu JSON tenha um tipo Typecript validado, você mesmo precisará fazer esse trabalho de validação. Isso não é novidade. Em Javascript simples, você precisa fazer o mesmo.
Gosto de expressar minha lógica de validação como um conjunto de "transformações". Eu defino a Descriptor
como um mapa de transformações:
type Descriptor<T> = {
[P in keyof T]: (v: any) => T[P];
};
Então, posso criar uma função que aplicará essas transformações a uma entrada arbitrária:
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;
}
Agora, não estou apenas validando minha entrada JSON, mas estou construindo um tipo de escrita à medida que prossigo. Os tipos genéricos acima garantem que o resultado infere os tipos de suas "transformações".
No caso de a transformação gerar um erro (que é como você implementaria a validação), gostaria de envolvê-la com outro erro mostrando qual chave causou o erro.
Em seu exemplo, eu usaria isso da seguinte maneira:
const value = pick(JSON.parse('{"name": "Bob", "error": false}'), {
name: String,
error: Boolean,
});
Agora value
serão digitados, visto que String
e Boolean
são ambos "transformadores" no sentido de que recebem a entrada e retornam uma saída digitada.
Além disso, o value
será realmente desse tipo. Em outras palavras, se name
fosse realmente 123
, ele será transformado para "123"
para que você tenha uma string válida. Isso ocorre porque usamos String
em tempo de execução, uma função integrada que aceita entrada arbitrária e retorna umstring
.
Você pode ver isso funcionando aqui . Tente o seguinte para se convencer:
const value
definição para ver se o pop-over mostra o tipo correto."Bob"
para 123
e execute novamente a amostra. No console, você verá que o nome foi convertido corretamente para a string "123"
.name
fosse realmente 123
, ele será transformado para "123"
. Isso parece estar incorreto. Meu não value
voltará quando eu copiar e colar todo o seu código exatamente e fizer essa alteração.{name: 123..
{name:"123"..
123
vez de "Bob"
).
Transformed
tipo. Você pode apenas usar Object
. type Descriptor<T extends Object> = { ... };
Transformed
tipo é totalmente desnecessário. Eu atualizei a resposta de acordo.
Além disso, você pode usar bibliotecas que realizam validação de tipo de seu json, como Sparkson . Eles permitem que você defina uma classe TypeScript, para a qual você gostaria de analisar sua resposta, no seu caso poderia ser:
import { Field } from "sparkson";
class Response {
constructor(
@Field("name") public name: string,
@Field("error") public error: boolean
) {}
}
A biblioteca irá validar se os campos obrigatórios estão presentes na carga JSON e se seus tipos estão corretos. Ele também pode fazer várias validações e conversões.
Há uma ótima biblioteca para isso ts-json-object
No seu caso, você precisaria executar o seguinte código:
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});
Esta biblioteca irá validar o json antes de analisar
JSON.parse
está disponível em TypeScript, então você pode apenas usá-lo:
JSON.parse('{"name": "Bob", "error": false}') // Returns a value of type 'any'
No entanto, você frequentemente desejará analisar um objeto JSON enquanto verifica se ele corresponde a um determinado tipo, em vez de lidar com um valor do tipo any
. Nesse caso, você pode definir uma função como a seguinte:
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;
}
Essa função pega uma string JSON e um objeto contendo funções individuais que carregam cada campo do objeto que você está criando. Você pode usá-lo assim:
const value = parse_json(
'{"name": "Bob", "error": false}',
{ name: String, error: Boolean, }
);