So i'm just starting out with typescript and something that I can't find much info on is how to deal with types for api call responses.
Lets say, I make a GET request to an api and it returns a JSON object for example:
{
name: "john",
age: 12
}
in my code, if i wanted to interact with response.name would i have to create an interface like below to use response.name without having a red linter under it?
interface Response {
name: string,
age: number
}
or is there a easier way to do it as some api calls would return JSON > 10 lines long and copying out the whole structure for every type of call seems very troublesome. Another thought I had was to create the interface but only have values I would use instead of the whole structure, but i'm not too sure so any help would be greatly appreciated!
You can define Response as a "common" type, that supports all types of API json response.
interface IResponse {
[key: string]: any
}
Now, you can type response.name without the "red line", also response.not_exist_property is valid.
My recommendation is to define all types for all API response type:
interface GetUserResponse {
name: string,
age: number,
}
for GET /users/:id (example)
You can use this tool to convert a json response to Typescript type.
I am trying to parse the response given by a HTTP endpoint using json4s in scala. The Json returned could have many number of fields (They are all documented and defined, but there are a lot of them, and they are subject to change.) I don't need to reference many of these fields, only pass them on to some other service to deal with.
I want to take the fields I need, and deserialise the rest to a map. The class needs to be serialised correctly also.
e.g. JSON response from endpoint:
{
"name": "value",
"unknown_field": "unknown",
"unknown_array": ["one", "two", "three"],
...
...
}
e.g. case class used in code:
case class TestResponse(name: String, otherFields: Map[String, Any])
Is there a simple solution for this?
I have made an attempt to implement a custom Serialiser for this, but have not had much luck as yet. Seems like this would be a common enough requirement. Is there a way to do this OOTB with json4s?
Cheers
Current attempt at customer serialiser:
private object TestResponseDeserializer extends CustomSerializer[TestResponse](_ => ( {
case JObject(JField("name_one", JString(name)) :: rest) => TestType1Response(name, rest.toMap)
case JObject(JField("name_two", JString(name)) :: rest) => TestType2Response(name, rest.toMap)
}, {
case testType1: TestType1Response=>
JObject(JField("name_one", JString(testType1.name)))
case testType2: TestType2Response=> JObject(JField("name_two", JString(testType2.name)))
}))
I was able to solve this using the custom serialiser in the original question. It didn't work for me due to an unrelated issue.
I'm trying to check in my integration test if all of values from some specific property has the same type. I was trying to do it along with jsonPath and JsonPathResultMatchers but without success. Finally in I did something like this :
MvcResult result = mockMvc.perform(get("/weather/" + existingCity))
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String responseContent = result.getResponse().getContentAsString();
TypeRef<List<Object>> typeRef = new TypeRef<List<Object>>() {
};
List<Object> humidities = JsonPath.using(configuration).parse(responseContent).read("$.*.humidity", typeRef);
Assertions.assertThat(humidities.stream().allMatch(humidity -> humidity instanceof Integer)).isTrue();
But I wonder if exist some clearer way to do this, can the same result be achieved with JSONPath ? Or AssertJ has some method to find it without usage stream code
Just answering on the AssertJ part: Stream assertions are provided with some caveats as the Stream under test is converted to a List in order to be able to perform multiple assertions (otherwise you can't as a Stream can only be consumed once).
Javadoc: assertThat(BaseStream)
Example:
assertThat(DoubleStream.of(1, 2, 3)).isNotNull()
.contains(1.0, 2.0, 3.0)
.allMatch(Double::isFinite);
I have happily used https://github.com/lukas-krecan/JsonUnit to check JSON, you can give it a try and see if you like it.
I personnaly would rather validate it against a JSON schema. There are Java validator implementations that could help you
I'm calling a REST service using Refit and I want to deserialize the JSON that is returned as a dynamic type.
I tried defining the interface as
[Get("/foo")]
Task<dynamic> GetFoo();
but the call times out.
I know I can deserialize to a dynamic like this
var mockString = "{ title: { name: 'fred', book: 'job'} }";
dynamic d = JsonConvert.DeserializeObject(mockString);
but I can't figure out what to pass to Refit to get it to do the same.
Another option would be to get Refit to pass the raw JSON back so I can deserialize it myself but I can't see a way to do that either.
Any ideas?
You can define your interface to return a string and get the raw JSON that way:
[Get("/foo")]
Task<string> GetFoo();
As described here:
https://github.com/paulcbetts/refit#retrieving-the-response
Refit uses JSON.NET under the hood, so any deserialization that works with that will work with Refit, including dynamic. The interface you have described is exactly right.
Here's a real working example:
public interface IHttpBinApi
{
[Get("/ip")]
Task<dynamic> GetIp();
}
var value = await RestService.For<IHttpBinApi>("http://httpbin.org").GetIp();
If you are using iOS and Refit 4+, you might be seeing this bug: https://github.com/paulcbetts/refit/issues/359
As Steven Thewissen has stated, you can use Task<string> as your return type (or Task<HttpResponseMessage>, or even Task<HttpContent>) to receive the raw response and deserialize yourself, but you shouldn't have to -- the whole point of Refit is that it's supposed to save you that hassle.
I'm trying to build a restful API and I'm struggling on how to serialize JSON data to a HTTP query string.
There are a number of mandatory and optional arguments that need to be passed in the request, e.g (represented as a JSON object below):
{
"-columns" : [
"name",
"column"
],
"-where" : {
"-or" : {
"customer_id" : 1,
"services" : "schedule"
}
},
"-limit" : 5,
"return" : "table"
}
I need to support a various number of different clients so I'm looking for a standardized way to convert this json object to a query string. Is there one, and how does it look?
Another alternative is to allow users to just pass along the json object in a message body, but I read that I should avoid it (HTTP GET with request body).
Any thoughts?
Edit for clarification:
Listing how some different languages encodes the given json object above:
jQuery using $.param: -columns[]=name&-columns[]=column&-where[-or][customer_id]=1&-where[-or][services]=schedule&-limit=5&return=column
PHP using http_build_query: -columns[0]=name&-columns[1]=column&-where[-or][customer_id]=1&-where[-or][services]=schedule&-limit=5&return=column
Perl using URI::query_form: -columns=name&-columns=column&-where=HASH(0x59d6eb8)&-limit=5&return=column
Perl using complex_to_query: -columns:0=name&-columns:1=column&-limit=5&-where.-or.customer_id=1&-where.-or.services=schedule&return=column
jQuery and PHP is very similar. Perl using complex_to_query is also pretty similar to them. But none look exactly the same.
URL-encode (https://en.wikipedia.org/wiki/Percent-encoding) your JSON text and put it into a single query string parameter. for example, if you want to pass {"val": 1}:
mysite.com/path?json=%7B%22val%22%3A%201%7D
Note that if your JSON gets too long then you will run into a URL length limitation problem. In which case I would use POST with a body (yes, I know, sending a POST when you want to fetch something is not "pure" and does not fit well into the REST paradigm, but neither is your domain specific JSON-based query language).
There is no single standard for JSON to query string serialization, so I made a comparison of some JSON serializers and the results are as follows:
JSON: {"_id":"5973782bdb9a930533b05cb2","isActive":true,"balance":"$1,446.35","age":32,"name":"Logan Keller","email":"logankeller#artiq.com","phone":"+1 (952) 533-2258","friends":[{"id":0,"name":"Colon Salazar"},{"id":1,"name":"French Mcneil"},{"id":2,"name":"Carol Martin"}],"favoriteFruit":"banana"}
Rison: (_id:'5973782bdb9a930533b05cb2',age:32,balance:'$1,446.35',email:'logankeller#artiq.com',favoriteFruit:banana,friends:!((id:0,name:'Colon Salazar'),(id:1,name:'French Mcneil'),(id:2,name:'Carol Martin')),isActive:!t,name:'Logan Keller',phone:'+1 (952) 533-2258')
O-Rison: _id:'5973782bdb9a930533b05cb2',age:32,balance:'$1,446.35',email:'logankeller#artiq.com',favoriteFruit:banana,friends:!((id:0,name:'Colon Salazar'),(id:1,name:'French Mcneil'),(id:2,name:'Carol Martin')),isActive:!t,name:'Logan Keller',phone:'+1 (952) 533-2258'
JSURL: ~(_id~'5973782bdb9a930533b05cb2~isActive~true~balance~'!1*2c446.35~age~32~name~'Logan*20Keller~email~'logankeller*40artiq.com~phone~'*2b1*20*28952*29*20533-2258~friends~(~(id~0~name~'Colon*20Salazar)~(id~1~name~'French*20Mcneil)~(id~2~name~'Carol*20Martin))~favoriteFruit~'banana)
QS: _id=5973782bdb9a930533b05cb2&isActive=true&balance=$1,446.35&age=32&name=Logan Keller&email=logankeller#artiq.com&phone=+1 (952) 533-2258&friends[0][id]=0&friends[0][name]=Colon Salazar&friends[1][id]=1&friends[1][name]=French Mcneil&friends[2][id]=2&friends[2][name]=Carol Martin&favoriteFruit=banana
URLON: $_id=5973782bdb9a930533b05cb2&isActive:true&balance=$1,446.35&age:32&name=Logan%20Keller&email=logankeller#artiq.com&phone=+1%20(952)%20533-2258&friends#$id:0&name=Colon%20Salazar;&$id:1&name=French%20Mcneil;&$id:2&name=Carol%20Martin;;&favoriteFruit=banana
QS-JSON: isActive=true&balance=%241%2C446.35&age=32&name=Logan+Keller&email=logankeller%40artiq.com&phone=%2B1+(952)+533-2258&friends(0).id=0&friends(0).name=Colon+Salazar&friends(1).id=1&friends(1).name=French+Mcneil&friends(2).id=2&friends(2).name=Carol+Martin&favoriteFruit=banana
The shortest among them is URL Object Notation.
How about you try this sending them as follows:
http://example.com/api/wtf?
[-columns][]=name&
[-columns][]=column&
[-where][-or][customer_id]=1&
[-where][-or][services]=schedule&
[-limit]=5&
[return]=table&
I tried with a REST Client
And on the server side (Ruby with Sinatra) I checked the params, it gives me exactly what you want. :-)
Another option might be node-querystring. It also uses a similar scheme to the ones you've so far listed.
It's available in both npm and bower, which is why I have been using it.
Works well for nested objects.
Passing complex objects as query parameters of a url.
In the example below, obj is the JSON object to pass into query parameters.
Injecting JSON object as query parameters:
value = JSON.stringify(obj);
URLSearchParams to convert a string to an object representing search params. toString to retain string type for appending to url:
queryParams = new URLSearchParams(value).toString();
Pass the query parameters using template literals:
url = `https://some-url.com?key=${queryParams}`;
Now url will contain the JSON object as query parameters under key (user-defined name)
Extracing JSON from url:
This is assuming you have access to the url (either as string or URL object)
url_obj = new URL(url); (only if url is NOT a URL object, otherwise ignore this step)
Extract all query parameters in the url:
queryParams = new URLSearchParams(url_obj.search);
Use the key to extract the specific value:
obj = JSON.parse(queryParams.get('key').slice(0, -1));
slice() is used to extract a tailing = in the query params which is not required.
Here obj will be the same object passed in the query params.
I recommend to try these steps in the web console to understand better.
You can test with JSON examples here: https://json.org/example.html