I am working on an iOS app using the MonoTouch framework. I am using Visual Studio 2010 Professional SP1 with the Xamarin.iOS (v1.3.250) extension. I have been able to open a valid FacebookConnect.FBSession by using the FacebookConnect.FBLoginView with no issues but when I try to make a Graph API request using FacebookConnect.FBRequest I recieve a non-standard JSON style string. When I run following request through the Graph API Explorer:
me?fields=albums.fields(id,name,cover_photo)
I receive the following response:
{
"id": "111111111111111111",
"albums": {
"data": [
{
"id": "111111111111111111",
"name": "Some Album (#1)",
"cover_photo": "111111111111111111",
"created_time": "000-00-00T00:00:00+0000"
},
{
"id": "111111111111111111",
"name": "Some Album (#2)",
"cover_photo": "111111111111111111",
"created_time": "000-00-00T00:00:00+0000"
},
],
"paging": {
"cursors": {
"after": "xxxxxxxx=",
"before": "xxxxxxxx="
}
}
}
}
Now all of this is just fine and is what I expect to receive but when I make the same Graph API request from my app like this:
public static void GetPhotoAlbums(string _userID)
{
// _userID = "me"
mFBRequest = new FBRequest(FBSession.ActiveSession, _userID + "?fields=albums.fields(id,name,cover_photo)");
FBRequestConnection fbRequestConnection = new FBRequestConnection();
fbRequestConnection.AddRequest(mFBRequest, OnPhotoAlbumsReceived);
fbRequestConnection.Start();
}
static void OnPhotoAlbumsReceived(FBRequestConnection _connection, NSObject _result, NSError _error)
{
if (_error == null)
{
Console.WriteLine("FacebookManager.OnPhotoAlbumsReceived() - JSON: " + _result.Description);
object o = JsonConvert.DeserializeObject(_result.Description);
// ...
}
}
I receive this JSON 'like' response:
{
albums = {
data = (
{
"cover_photo" = 111111111111111111;
"created_time" = "000-00-00T00:00:00+0000";
id = 111111111111111111;
name = "Some Album (#1)";
},
{
"cover_photo" = 111111111111111111;
"created_time" = "000-00-00T00:00:00+0000";
id = 111111111111111111;
name = "Some Album (#2)";
},
);
paging = {
cursors = {
after = "xxxxxxxx=";
before = "xxxxxxxx=";
};
};
};
"id": "111111111111111111";
}
I'm not really sure how/why I'm getting a response formatted in a non-standard way but needless to say, I get Newtonsoft.Json.JsonReaderException when attempting to deserialize the data because it does not follow the standard formatting rules (ie, = instead of : to separate key/value pairs, ; instead of , to separate elements of a container, some keys having quotes while others do not, etc...)
I'm pretty new to Facebook and JSON stuff in general and am really at a loss for what is happening to the response string I receive. Any help, feedback, ideas are greatly appreciated. Thanks.
* Solution *
After a bunch of digging around it seems to be that the Graph API response is indeed JSON but it gets converted to an FBGraphObject which holds a NSMutableArray as it the data makes its way through the MonoTouch->.NET bindings so when I pulled _result.Description (equivalent to _result.ToString() it returned me the string representation of that object which happens to look a lot like JSON but is not. After finding all this out (and a lot of runtime experimentation), I was finally able to extract the data into a usable state by doing this:
static void OnPhotoAlbumsReceived(FBRequestConnection _connection, NSObject _result, NSError _error)
{
if (_error == null)
{
NSArray fieldData = (NSArray) _result.ValueForKeyPath(new NSString("albums.data.name"))
string[] names = NSArray.StringArrayFromHandle(fieldData.Handle);
// ...
}
}
Although this works for me, I have a feeling that there is a better or more robust way to get the data I requested, so if any developers out there can offer any additional tips for improving this solution, I would love to hear them.
As stated in Facebook SDK documentation Graph API:
When a request returns a non-JSON response (such as a "true" literal),
that response will be wrapped into a dictionary using this const as
the key. This only applies for very few Graph API prior to v2.1.
So you can check first if result is an NSDictionary, otherwise you can deserialize the JSON data as usual.
Below some obj-c code you can translate into C#/MonoTouch (I don't know the framework, I hope it is helpful).
NSDictionary *dict;
if ([graphResult isKindOfClass:[NSDictionary class]]) {
dict = (NSDictionary *)graphResult;
} else {
NSError *JSONError;
dict = [NSJSONSerialization JSONObjectWithData:graphResult options:NSJSONReadingAllowFragments error:&JSONError];
if (JSONError) {
NSLog(#"Facebook: JSON parse error: %#", JSONError);
// Handle error
}
}
Related
I'm using Volley library to communicate with my API. I'm pretty new to Android and Kotlin and I'm really confused about extracting keys from the following JSON data
{
"message": {
"_id": "60bc7fa7abeedb25643fa692",
"hash": "3a54b415461a63abac1fc6dfa0e140584047bd15358e33a177f9505ed2faa4d4",
"blockchain": "ethereum",
"amount": 5000,
"amount_usd": 13352971,
"from": "d3d69228cb2292f933572399593617f574c70eb1",
"to": "fe9996da73d6bf5252f15024811954ae37ab68be",
"__v": 0
}
}
The volley library returns all of this JSON data in a variable called response and I'm using response.getString("message") to extract the message key but, I don't understand how to extract the internal data such as hash, blockchain, amount, etc.
I'm using the following code to get the JSON data from my backend.
val jsonRequest = JsonObjectRequest(
Request.Method.GET, url, null,
{ response ->
tweet_text.setText(response.getString("message"))
Log.d("resp", response.toString())
},
{
Log.d("err", it.localizedMessage)
})
Any help would be appreciated, Thanks!
I found it, I just used the getJSONObject() method to make it work
val jsonRequest = JsonObjectRequest(
Request.Method.GET, url, null,
{ response ->
val txn = response.getJSONObject("message")
//txn object can be used to extract the internal data
},
{
Log.d("err", it.localizedMessage)
})
Consider the following json:
{
"title": "SOME TITEL",
"status": 500,
"detail": "Some detail",
"errors": [
{
"Parameter": "SOME VALUE",
"Code": "SOME CODE",
"Message": "SOME MESSAGE",
"Details": "SOME EXTRA DETAILS"
}
]
}
It is generated by an API response that construct a problem details like this:
var problemDetails = new ProblemDetails
{
Status = StatusCodes.Status500InternalServerError;
Detail = "DETAIL";
Title = "TITLE";
};
var customClass = new CustomCalss
{
Code = "INTERNAL_SERVER_ERROR",
Message = "Some message",
Details = "Extra details"
};
problemDetails.Extensions.Add(new KeyValuePair<string, object>("errors", new [] { customClass }));
When trying to deserialize the json to a problem details using System.Text.JsonSerialiser i found the following issues:
Status, Code and Title are not deserialised to the problem details properties, they are null
Extension data is not deserialized.
I'm testing this behavior like this:
var json = #"{
""title"": ""SOME TITLE"",
""status"": 500,
""detail"": ""Some detail"",
""errors"": [
{
""Parameter"": null,
""Code"": ""SOME CODE"",
""Message"": ""SOME MESSAGE"",
""Details"": ""SOME EXTRA DETAILS""
}
]
}";
var result = JsonSerializer.Deserialize<ProblemDetails>(json);
Assert.NotNull(result.Detail);
Assert.NotNull(result.Title);
var customClass = Assert.IsType<CustomCalss[]>(result.Extensions["errors"]);
var error = customClass.First();
Assert.Equal("INTERNAL_SERVER_ERROR", error.Code);
Any insights?
C# is a case sensitive language, something wrong with your JSON string, the key must exactly same with the property of the target class. I changed the "title" to "Title" and then got the correct value, attached here for your reference:
Updated on 11/20:
Did not noticed that it is a MVC internal class, we can simple reproduce this issue in local box now, but checked the source codes and known issues online, no existing related issue there. So I reported a new bug for this issue, attached here for your reference
https://github.com/aspnet/AspNetCore/issues/17250
Updated on 11/22:
This has been confirmed as a bug in ASP.NET Core 3.0, and fixed in 3.1 version, please upgrade to 3.1-preview2. Alternatively you could specify a custom JsonConverter based on the implementation we have in 3.1 as part of the JsonSerializerOptions that you pass when deserializing - https://github.com/aspnet/AspNetCore/blob/release/3.1/src/Mvc/Mvc.Core/src/Infrastructure/ValidationProblemDetailsJsonConverter.cs
Using the Spring WebClient I am calling a Rest API that gives me a response as a JSON, following is a structure of the response -
{
"vehicles":[
{ "name":"veh1", "type":"car", "age": 5 },
{ "name":"veh2", "type":"speedboat", "age":12},
.....
]
"metadata": {
"token":"abcd",
"days":120
}
}
I am a newbie to reactive programming, I have written the following code, which works fine ..
Mono<VehicleResponse> = webclient.get()
.uri("/legacy/vehicles")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, clientResponse -> Mono.empty())
.onStatus(HttpStatus::is5xxServerError, clientResponse -> Mono.empty())
.bodyToMono(VehicleResponse.class);
But, actually what I am interested in is, only the vehicles array (don't want the metadata information.) Is it possible to obtain / read only the vehicles (array) as a Flux of Vehicle ?
When we have many documents to be returned from the response, we can paginate the result and give the link in the response to retrieve the next set of documents to be fetched from the API. However my case is different, each document in my response can be very large. How do I deal with such response ?
For Example consider the sample Json below:
{
"excludes": null,
"endDate": 1422918663,
"appliedText": "Applied text",
"id": "135699543",
...
"assignedTo": {
"productPart": [
{
"part": "1"
},
{
"part": "2"
},
{
"part": "3"
},
{
"part": "4"
},
...
]
}
...
}
If you see here, the productPart can grow to tens of thousands. How can I restrict such large response yet retain the structure.
Fetch product parts with a separate request (if you have the possibility) which then in turn can be paginated.
Alt. return the first x product parts and then provide a link where more product parts can be obtained.
I believe you should try something like JSON-Streaming.
Streaming Processing (also known as Incremental Processing) is the most efficient way to process JSON content. It has the lowest memory and processing overhead, and can often match performance of many binary data formats available on Java platform
Here is the main idea:
try {
InputStream in = [..];
JsonParser parser = new JsonFactory().createParser(in);
JsonToken token = null;
while ((token = parser.nextToken()) == JsonToken.FIELD_NAME) {
/*
* Parse parts and store them in client DB
* use later client side pagination
*
* {
* "part": "i"
* }
*/
}
} catch (Exception e) {
//
}
The topic of this post is: my solution is too slow for a large query return.
I have a Web Api serving REST results like below from a call to localhost:9090/api/invetories?id=1:
[
{
"inventory_id": "1",
"film_id": "1",
"store_id": "1",
"last_update": "2/15/2006 5:09:17 AM"
},
{
"inventory_id": "2",
"film_id": "1",
"store_id": "1",
"last_update": "2/15/2006 5:09:17 AM"
}
]
Since my WebAPI did not provide a root key for my JSON response, I made a RESTSerializer like following.
export default DS.RESTSerializer.extend({
extract:function(store,primaryType,payload,id,requestType){
var typeName = primaryType.typeKey;
var data = {};
data[typeName] = payload; // creating root
payload = data;
return this._super(store,primaryType,payload,id,requestType)
}
});
When this gets run, I get the following error message: Assetion failed: You must include an 'id' for inventory in an object passed to 'push'
As you can see, these objects do not have the attribute id, so I found that the default behaviour of Ember RESTSerializer forces me to write my own.
Okay, so here's where I'm not sure my solution is right. inventory_id from my return is unique, therefore I choose to use that as an id, okay I'm thinking to my self, I'll just add it manually. The function looks like this now:
export default DS.RESTSerializer.extend({
extract:function(store,primaryType,payload,id,requestType){
var typeName = primaryType.typeKey;
for(var i=0;i<payload.length;i++){
payload[i].id = payload[i].inventoryId;
}
var data = {};
data[typeName] = payload; // creating root
payload = data;
return this._super(store,primaryType,payload,id,requestType)
}
});
By just manually duplicating an attribute, I feel like I'm cheating my way over this error message. In addition, I sometimes return a large payload array (over 150k rows). Looping O(n) just doesn't seem a right price to pay for just a simple mapping.
Is there some other way to set either my WebAPI or serializer up so I avoid the for loop in assigning the id that ember so desperately wants.
I think this should fix your problem:
export default DS.RESTSerializer.extend({
primaryKey: 'inventory_id'
});
With this parameter Ember Data will map inventory_id to it's id parameter.