What is the JSON interface in Typescript - json

Why does the following snippet not compile, and how is the ES6 JSON interface supposed to be used?
let myVar: JSON = {"id": "12"};
Gives the following error message: Type '{ id: string; }' is not assignable to type 'JSON'. Object literal may only specify known properties, and '"id"' does not exist in type 'JSON'.
My IDE gives the following definition for JSON, but I can’t understand it:
interface JSON {
readonly [Symbol.toStringTag]: string;
}

JSON is a global object defined by the JS specification designed to hold the parse and stringify methods for converting between JS data structures and JSON texts.
It isn't a type. It isn't supposed to be used as one.
When creating a custom object format, you are supposed to define your own type (although it isn't useful to do so here, but might be if you define a function elsewhere that you need to pass the object to as an argument). When dealing with JSON, you are dealing with strings.
type MyFormat = {
id: string;
}
let myVar: MyFormat = {"id": "12"};
let myJSON: string = JSON.stringify(myVar);

There's no reason you'd use the JSON interface in your code. It relates to the JSON built-in object, and there's no reason to use that object other than using its parse and/or stringify methods to parse or create JSON text.
From your code, you appear to misunderstand what JSON is. (Lots of people do! :-) ) JSON is a textual notation for data exchange. (More here.) If you're dealing with JavaScript or TypeScript source code, and not dealing with a string, you're not dealing with JSON.
Your myVar refers to an object. There's no need to put a type annotation on it, you can just let TypeScript infer it from the initializer, but if you wanted to put a type annotation on it you'd use either {id: string;} or Record<string, string> or some other object type:
// Letting TypeScript infer
let myVar = {"id": "12"};
// Or specify an object with an `id` property of type string
let myVar: {id: string;} = {"id": "12"};
// Or create a type alias and use it
type MyVarType = {
id: string;
};
let myVar: MyVarType = {"id": "12"};
// Or perhaps an object where any string is a valid property name and the types are all strings
let myVar: Record<string, string> = {"id": "12"};
See the documentation linked above for more about object types.
Side note: If you meant to use a number for id, you'd use id: number or similar:
let myVar: {id: number;} = {id: 12};

Related

Parsing Vector to Array String

I'm new to Rust.
I try to write a websocket client.
This is my format message:
"[integer, "string", object]"
Is there away to store all value in Vector something like:
let msg: Vec<interface> = vec![123, "event", AnyObject{}];
How to convert it into string and vice versa.
Thanks in advance
Conceptually speaking you want to use an enum. The type of enums used in rust are called tagged unions. Essentially you can think of them as enums which can hold data.
enum Interface {
Int(i32),
String(&'static str),
Object(AnyObject),
// etc
}
// You can then create a vec of different enum variants
let msg: Vec<Interface> = vec![Interface::Int(123), Interface::String("event"), Interface::Object(AnyObject{})];
Assuming you are referring to JSON, then the recommended solution is to use serde with serde_json. serde_json provides a Value enum you can use to represent JSON data in an unknown layout.
// Copied from serde_json docs
enum Value {
Null,
Bool(bool),
Number(Number),
String(String),
Array(Vec<Value>),
Object(Map<String, Value>),
}
However, you don't need to use Value directly (unless you want to) since serde_json provides a macro to make this easier by letting you format your code like JSON.
use serde_json::{json, Value};
let msg: Value = json!([123, "event", {}]);
// You can then serialize a Value into JSON format
println!("{:?}", msg.to_string());
// Output: "[123,\"event\",{}]"
I recommend reading through the overview since they have a bunch of convenient ways for working with JSON.

how to get value from json via variable in typescript?

supposing I have to read some data from some json files(i18n), every json file may look like:
{
"foo": "1",
"bar": "2",
...
}
I don't know how many fields this json have(it can be expanded), but it's fields look like
{
[prop: string]: string
}
besides, all the json files share the same fields.
when I try to read a value from this json via:
//a can be expanded, I'm not sure how many fileds does it have
let a = {
name: "dd",
addr: "ee",
}
//I'm confident a has a field "name"
let b = "name";
console.log(a[b]);
the error message is:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type
how could I fix it?
The error you're encountering is because the keys in a is not just any string (in fact, it can only be "name" or "add"), but b can be a string of any arbitrary value. If you are very sure that b represents a key found in the object a, you can hint TypeScript as such:
let b: keyof typeof a = "name";
Attempting to assign any arbitrary string value to b will lead to an error:
// This will cause an error
let b: key typeof a = "foobar";
See proof-of-concept on TypeScript Playground.

How parse JSON response with axios and TypeScript to PascalCase model?

I have type like this:
export type Model = {
Id: number,
Name: string
}
and a JSON response like this: {"id": 0, "name": "User"}.
After Axios parsed that response (const response = await Axios.get<Model>(source)), I get next object:
Id: undefined Name: undefined id: 0 name: "User"
How correctly parse response to PascalCase model kind?
`
There are many ways to do this, but whatever happens, you need to change your types, as they're not correct at the moment, and you need to manually transform your result object.
The types currently say that Axios.get will return a model with Id and Name keys, which is definitely wrong (it will return a model with id and name keys). You can transform this result, but can't easily change the first return value there, so you need to correct that.
Once that's correct, you need to transform the JSON response to the model you want. One option is to use lodash, which makes this fairly easy.
A full example might look like this:
export type Model = {
Id: number,
Name: string
}
// Get the raw response, with the real type for that response
const response = await Axios.get<{ id: number, name: string }>(source);
// Transform it into a new object, with the type you want
const model: Model = _.mapKeys(response,
(value, key) => _.upperFirst(key) // Turn camelCase keys into PascalCase
);
There are lots of other ways to do the last transformation step though, this is just one option. You might also want to think about validation first too, to check the response data is the shape you expect, if that's a risk in your case.

Kotlin - Array property in data class error

I'm modelling some JSON - and using the following lines
data class Metadata(
val id: String,
val creators: Array<CreatorsModel>
)
along with:
data class CreatorsModel (
val role: String,
val name: String
)
However keep seeing the error: Array property in data class error.
Any ideas why this is?
FYI, the JSON looks like:
{
"id": "123",
"creators": [{
"role": "Author",
"name": "Marie"
}
]
}
In Kotlin you should aim to use List instead of Array where possible. Array has some JVM implications, and although the compiler will let you, the IDE may prompt you to override equals and hashcode manually. Using List will make things much simpler.
You can find out more about the difference here: Difference between List and Array types in Kotlin

How to properly use JSON.parse in kotlinjs with enums?

During my fresh adventures with kotlin-react I hit a hard stop when trying to parse some data from my backend which contains enum values.
Spring-Boot sends the object in JSON form like this:
{
"id": 1,
"username": "Johnny",
"role": "CLIENT"
}
role in this case is the enum value and can have the two values CLIENT and LECTURER. If I were to parse this with a java library or let this be handled by Spring-Boot, role would be parsed to the corresponding enum value.
With kotlin-js' JSON.parse, that wouldn't work and I would have a simple string value in there.
After some testing, I came up with this snippet
val json = """{
"id": 1,
"username": "Johnny",
"role": "CLIENT",
}"""
val member: Member = JSON.parse(json) { key: String, value: Any? ->
if (key == "role") Member.Role.valueOf(value.toString())
else value
}
in which I manually have to define the conversion from the string value to the enum.
Is there something I am missing that would simplify this behaviour?
(I am not referring to using ids for the JSON and the looking those up, etc. I am curious about some method in Kotlin-JS)
I have the assumption there is not because the "original" JSON.parse in JS doesn't do this and Kotlin does not add any additional stuff in there but I still have hope!
As far as I know, no.
The problem
Kotlin.JS produces an incredibly weird type situation when deserializing using the embedded JSON class, which actually is a mirror for JavaScript's JSON class. While I haven't done much JavaScript, its type handling is near non-existent. Only manual throws can enforce it, so JSON.parse doesn't care if it returns a SomeCustomObject or a newly created object with the exact same fields.
As an example of that, if you have two different classes with the same field names (no inheritance), and have a function that accepts a variable, it doesn't care which of those (or a third for that matter) it receives as long as the variables it tries accessing on the class exists.
The type issues manifest themselves into Kotlin. Now wrapping it back to Kotlin, consider this code:
val json = """{
"x": 1, "y": "yes", "z": {
"x": 42, "y": 314159, "z": 444
}
}""".trimIndent()
data class SomeClass(val x: Int, val y: String, val z: Struct)
data class Struct(val x: Int, val y: Int, val z: Int)
fun main(args: Array<String>) {
val someInstance = JSON.parse<SomeClass>(json)
if(someInstance.z::class != Struct::class) {
println("Incompatible types: Required ${Struct::class}, found ${someInstance.z::class}");
}
}
What would you expect this to print? The natural would be to expect a Struct. The type is also explicitly declared
Unfortunately, that is not the case. Instead, it prints:
Incompatible types: Required class Struct, found class Any
The point
The embedded JSON de/serializer isn't good with types. You might be able to fix this by using a different serializing library, but I'll avoid turning this into a "use [this] library".
Essentially, JSON.parse fails to parse objects as expected. If you entirely remove the arguments and try a raw JSON.parse(json); on the JSON in your question, you'll get a role that is a String and not a Role, which you might expect. And with JSON.parse doing no type conversion what so ever, that means you have two options: using a library, or using your approach.
Your approach will unfortunately get complicated if you have nested objects, but with the types being changed, the only option you appear to have left is explicitly parsing the objects manually.
TL;DR: your approach is fine.