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

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.

Related

React Typescript assigning local json to useState

I have a data.json file which I import by doing import * as data from './data/data.json';
in my App function, I am then doing const [jobsObject, setJobsObject] = useState([]);
To add it to my state, I am doing
useEffect(() => {
setJobsObject(data);
}, []);
I have seen the exact same code work in js. What I am getting is Typescript Error, which says Argument of type '({ "id": number; "company": string; [a lot of lines describing content of this object ommited for brevity] is not assignable to type 'never'.
Anybody has an idea of how to make it work?
The easiest fix for this is asserting that whatever type data has, also is the expected one for jobsObject. So say that const data: DataType where
type DataType = {
id: number;
company: string;
};
then you want to assign
const [jobsObject, setJobsObject] = useState<DataType | undefined>(undefined);
I added undefined due to lack of further information and this is a reasonable pattern when you lack initiating value of your state variable. I noticed that you prefer defined it as an array, as data seems to be an object here, that's the primary issue that needs resolving. If you in fact have data as an array of objects with the type DataType you can simply extend the type definition by DataType[] making it an array.

Determining the underlying type of a generic Type with TypeScript

Consider the following interface within TypeScript
interface IApiCall<TResponse> {
method: string;
url: string;
}
Which is then used within the following method;
const call = <TResponse>(api: IApiCall<TResponse>): void => {
// call to API via ajax call
// on response, grab data
// use JSON.parse(data) to convert to json object
return json as TResponse;
};
Now we use this for Type safety within our methods so we know what objects are being returned from the API. However, when we are returning a single string from the API, JSON.parse is converting the string '12345' into a number, which then breaks further down the line when we are trying to treat this as a string and use value.trim() yet it has been translated into a number.
So ideas to solve this so that we are not converting a string into a number.
How can we stop JSON.parse from converting a single string value into a number?
If using JSON.parse, we check the type of TResponse and compare it against the typeof of json generated.
if (typeof (json) !== typeof(TResponse))...
However there doesn't seem to be an obvious way to determine the generic type.
Question 1: How can we stop JSON.parse() from converting a single string value into a number?
JSON is a text format, so in JSON.parse(x), x needs to be a string. But JSON text represents data of not-necessarily-string types. It sounds like you might be making a category mistake, by confusing a thing with its representation.
If you convert the number 12345 to JSON (JSON.stringify(12345)) you will get the string "12345". If you parse that string, (JSON.parse("12345")), you will get the number 12345 back. If you wanted to get the string "12345", you need to encode it as JSON ( JSON.stringify("12345")) as the string "\"12345\"". If you parse that ( JSON.parse('"12345"') you will get the string "12345" out.
So the straightforward answer to the question "How can we stop JSON.parse() from converting a single string value into a number" is "by properly quoting it". But maybe the real problem is that you are using JSON.parse() on something that isn't really JSON at all. If you are given the string "12345" and want to treat it as the string "12345", then you don't want to do anything at all to it... just use it as-is without calling JSON.parse().
Hope that helps. If for some reason either of those don't work for you, you should post more details about your use case as a Minimal, Complete, and Verifiable example.
Question 2: How do we determine that the returned JSON-parsed object matches the generic type?
In TypeScript, the type system exists only at design time and is erased in the emitted JavaScript code that runs later. So you can't access interfaces and type parameters like TResponse at runtime. The general solution to this is to start with the runtime solution (how would you do this in pure JavaScript) and help the compiler infer proper types at design time.
Furthermore, the interface type IApiCall
interface IApiCall<TResponse> {
method: string;
url: string;
}
has no structural dependence on TResponse, which is not recommended. So even if we write good runtime code and try to infer types from it, the compiler will never be able to figure out what TResponse is.
In this case I'd recommend that you make the IApiCall interface include a member which is a type guard function, and then you will have to write your own runtime test for each type you care about. Like this:
interface IApiCall<TResponse> {
method: string;
url: string;
validate: (x: any) => x is TResponse;
}
And here's an example of how to create such a thing for a particular TResponse type:
interface Person {
name: string,
age: number;
}
const personApiCall: IApiCall<Person> = {
method: "GET",
url: "https://example.com/personGrabber",
validate(x): x is Person {
return (typeof x === "object") &&
("name" in x) && (typeof x.name === "string") &&
("age" in x) && (typeof x.age === "number");
}
}
You can see that personApiCall.validate(x) should be a good runtime check for whether or not x matches the Person interface. And then, your call() function can be implemented something like this:
const call = <TResponse>(api: IApiCall<TResponse>): Promise<TResponse | undefined> => {
return fetch(api.url, { method: api.method }).
then(r => r.json()).
then(data => api.validate(data) ? data : undefined);
};
Note that call returns a Promise<Person | undefined> (api calls are probably asynchronous, right? and the undefined is to return something if the validation fails... you can throw an exception instead if you want). Now you can call(personApiCall) and the compiler automatically will understand that the asynchronous result is a Person | undefined:
async function doPersonStuff() {
const person = await call(personApiCall); // no <Person> needed here
if (person) {
// person is known to be of type Person here
console.log(person.name);
console.log(person.age);
} else {
// person is known to be of type undefined here
console.log("File a missing Person report!")
}
}
Okay, I hope those answers give you some direction. Good luck!
Type annotations only exist in TS (TResponse will be nowhere within the output JS), you cannot use them as values. You have to use the type of the actual value, here it should be enough to single out the string, e.g.
if (typeof json == 'string')

How to extract information from angular output

If I log the full data from an Angular client I get
object { type: "message", target: {_}, errorCode: undefiend,
errorMessage: undefined, data: "{\"data\":[\"124",\"611\"]}",
lastEventId: ""}
I want to grab the {\"data\":[\"124",\"611\"]} part to send it as json to a client. Using JSON.parse(data.data) though gives me
data: "{\"data\":[\"124",\"611\"]}", lastEventId: ""}
Is it possible to just grab the "{\"data\":[\"124",\"611\"]}" since otherwise the client has problems with the deserialization.
Let's say you have your initial string in myobject_string.
Then, you extract the JSON to a Javascript object with: const myobject = JSON.parse(myobject_string).
Then, the data you are looking for is in myobject.data.
Look here for more example code on JSON.parse.

parse json response to typescript class

i know there are multiple similar topics, however trying their solutions doesn't give me expected result.
Input json string
data:"{"message": "{\"type\":\"CONTROL\",\"command\":\"REQUEST_STATUS_ALL\"}"}"
object declaration/parse:
const msg: Message = <Message>JSON.parse(data.data);
output:
{message: "{"type":"CONTROL","command":"REQUEST_STATUS_ALL"}"}
-values are not properly assigned, but instead in a text form.
the same object looks like this if it's initialized manually(in TS):
MessageĀ {type: "CONTROL", status: undefined, command: "REQUEST_STATUS_ALL", body: undefined}
What is the correct way to parse that json string into the Message object?
Thank you!
It seems the value for message was improperly encoded as a string. Calling JSON.parse a second time on the message property will get the result you want, though you might want to fix the underlying cause of the improperly encoded data instead.
parseMessage(data: string) {
const msgTemp = JSON.parse(data);
msgTemp.message = JSON.parse(msgTemp.message);
return <Message>msgTemp;
}
const msg = parseMessage(data.data);

Backbone.js .save() JSON response attribute issue

So my issue is this.
Using backbone to save something in a MYSQL Database.
When I call this.model.save() I am getting a very weird issue.
The model will save the JSON response as an object and will not update the new values instead.
So my attributes in development tools will look something like this.
attributes: Object
0: Object
ID: "4"
Name: "TEST"
Title: "MEOW"
Stuff: "1"
When: "2013-02-14 22:17:14"
The 0 should not be there. I did confirm that the json object is valid so I know that is not the issue here.
It looks like your JSON response is actually an array with a single element, not an object.
The property 0 is created when Backbone calls model.set(response), which in turn copies all keys of the response object to the attributes hash. If an array is passed to set, this is what happens.
You should fix your server to respond with a raw object ({...}) instead of an array ([{...}]). If you're not able to change the server behaviour, you can override Model.parse to unwrap the response on the client:
var Model = Backbone.Model.extend({
parse: function(response) {
return _.isArray(response) ? response[0] : response;
}
});