When deserializing into a dynamic I can get a response but when I try to deserialize into a typed object I get nothing back.
Types being serialized
public class User {
public string Forename { get; set; }
}
public class UserCollectionResponseType {
public List<User> Items { get; set; }
}
Request being made:
var response = await graphQLHttpClient.SendQueryAsync<UserCollectionResponseType >(this.request).ConfigureAwait(false);
Sample response from API
{
"data": {
"users": {
"items": [
{
"forename": "4212hotp0i"
},
{
"forename": "XO - Test"
},
{
"forename": "5422q5htsd"
},
{
"forename": "XR - Test"
},
{
"forename": "2035snyyex"
},
{
"forename": "2379plsmri"
},
{
"forename": "7100e1igjl"
},
{
"forename": "94 - Test"
},
{
"forename": "ZX - Test"
},
{
"forename": "Test"
}
]
}
}
}
I was missing the Data part of the Json when trying to deserialize into an object.
My final objects looked like this:
public partial class Data {
[JsonProperty("users")]
public Users Users { get; set; }
}
public partial class Users {
[JsonProperty("items")]
public Item[] Items { get; set; }
}
public partial class Item {
[JsonProperty("forename")]
public string Forename { get; set; }
}
Related
I have the following JSON which I ran through a validator and 'Pasted JSON as classes' to build a model from the schema in Visual Studio:
{
"entity": {
"Successful Requests": [
{
"Status": 201,
"Resource ID": 34715818,
"Message": "Created resource 1 at URI: /rest/equipment/ids/34715818"
},
{
"Status": 201,
"Resource ID": 34715838,
"Message": "Created resource 2 at URI: /rest/equipment/ids/34715838"
}
],
"Failed Requests": [
{
"Status": 500,
"Resource ID": -1,
"Message": "Failed to create new resource 1. Bulk update failed. Cause: Template 'xxx' not found."
},
{
"Status": 500,
"Resource ID": -1,
"Message": "Failed to create new resource 2. Bulk update failed. Cause: Template 'xxx' not found."
}
]
},
"variant": {
"language": null,
"mediaType": {
"type": "application",
"subtype": "json",
"parameters": {},
"wildcardType": false,
"wildcardSubtype": false
},
"encoding": null,
"languageString": null
},
"annotations": [],
"mediaType": {
"type": "application",
"subtype": "json",
"parameters": {},
"wildcardType": false,
"wildcardSubtype": false
},
"language": null,
"encoding": null
}
This builds the following classes (I added the JsonProperties in the entity class because the property names contain spaces, I also tried without them and I get the same error):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace xxx.Data.Models
{
public class Rootobject
{
public Entity entity { get; set; }
public Variant variant { get; set; }
public object[] annotations { get; set; }
public Mediatype1 mediaType { get; set; }
public object language { get; set; }
public object encoding { get; set; }
}
public class Entity
{
[JsonProperty("Successful Requests")]
public SuccessfulRequest[] SuccessfulRequests { get; set; }
[JsonProperty("Failed Requests")]
public FailedRequest[] FailedRequests { get; set; }
}
public class SuccessfulRequest
{
public int Status { get; set; }
public int ResourceID { get; set; }
public string Message { get; set; }
}
public class FailedRequest
{
public int Status { get; set; }
public int ResourceID { get; set; }
public string Message { get; set; }
}
public class Variant
{
public object language { get; set; }
public Mediatype mediaType { get; set; }
public object encoding { get; set; }
public object languageString { get; set; }
}
public class Mediatype
{
public string type { get; set; }
public string subtype { get; set; }
public Parameters parameters { get; set; }
public bool wildcardType { get; set; }
public bool wildcardSubtype { get; set; }
}
public class Parameters
{
}
public class Mediatype1
{
public string type { get; set; }
public string subtype { get; set; }
public Parameters1 parameters { get; set; }
public bool wildcardType { get; set; }
public bool wildcardSubtype { get; set; }
}
public class Parameters1
{
}
}
I make an API call in code:
var response = await _repositorySvc.PutBulkEquipment(requestBody, Guid.NewGuid());
var result = response.Content.ReadAsStringAsync().Result;
_logger.LogWarning("------------------Result:");
_logger.LogWarning(result);
Which returns the following JSON string in the console:
{
"entity": "{\"Successful Requests\":[{\"Status\":201,\"Resource ID\":34715872,\"Message\":\"Created resource 1 at URI: /rest/equipment/ids/34715872\"},{\"Status\":201,\"Resource ID\":34715892,\"Message\":\"Created resource 2 at URI: /rest/equipment/ids/34715892\"}],\"Failed Requests\":[]}",
"variant": {
"language": null,
"mediaType": {
"type": "application",
"subtype": "json",
"parameters": {},
"wildcardType": false,
"wildcardSubtype": false
},
"encoding": null,
"languageString": null
},
"annotations": [],
"mediaType": {
"type": "application",
"subtype": "json",
"parameters": {},
"wildcardType": false,
"wildcardSubtype": false
},
"language": null,
"encoding": null
}
Then, when I attempt to deserialize the string to the Entity class:
var deserializedResponse = JsonConvert.DeserializeObject<Rootobject>(result);
I get the following error:
Error converting value
"{"Successful Requests":[{"Status":201,"Resource ID":34715872,"Message":"Created resource 1 at URI: /rest/equipment/ids/34715872"},{"Status":201,"Resource ID":34715892,"Message":"Created resource 2 at URI: /rest/equipment/ids/34715892"}],"Failed Requests":[]}"
to type 'xxx.Data.Models.Entity'.
Path 'entity', line 1, position 290.
Would anyone be able to spot any mistakes I'm making that I might be missing in the above scenario that could be contributing to me banging my head on a wall for the past day and a half on this? Everything looks correct to me and I'm stumped - I do a lot of deserialization and usually it's ez-mode so I'm not sure what I'm missing.
Is there any way to get better insight into deserialization issues in .NET Core?
Thank you!
If this is the JSON response:
"entity": "{\"Successful Requests\":[...],\"Failed Requests\":[]}",
then entity is of type string. Note the quotes in the beginning and the end. Also note that all the inner quotes are escaped using backslashes.
And the error message says exactly that, it cannot convert that string into an object. it did expect something more like
"entity": {"Successful Requests":[...], "Failed Requests":[]},
I'm using the example on https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/custom-serialization.html#_serializing_type_information to get $type information for the documents in elasticsearch.
However as mentioned on the page this only returns type information for the outer document:
the type information is serialized for the outer MyDocument instance, but not for each MySubDocument instance in the SubDocuments collection.
So my question now is if anyone knows how to also get type information for sub documents?
I've tried using the same JsonSerializerSettings as in their example separate from Elasticsearch (using LinqPad) and there I get type information also for sub documents:
void Main()
{
var temp = new ListBlock
{
Id = 1,
Title = "Titel",
Blocks = new List<BlockContent> {
new InnerBlock {
Id = 11,
MyProperty ="Inner Property"
},
new InnerBlock2 {
Id = 12,
MyProperty2 = "Inner property 2"
}
}
};
var serializeOptions = new Newtonsoft.Json.JsonSerializerSettings
{
TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
TypeNameAssemblyFormatHandling = Newtonsoft.Json.TypeNameAssemblyFormatHandling.Simple,
Formatting = Newtonsoft.Json.Formatting.Indented
};
var serialized = Newtonsoft.Json.JsonConvert.SerializeObject(temp, serializeOptions);
serialized.Dump();
}
public class BlockContent
{
public int Id { get; set; }
}
public class ListBlock : BlockContent
{
public string Title { get; set; }
public List<BlockContent> Blocks { get; set; }
}
public class ListBlock2 : BlockContent
{
public string Title2 { get; set; }
public List<BlockContent> Blocks { get; set; }
}
public class InnerBlock : BlockContent
{
public string MyProperty { get; set; }
}
public class InnerBlock2 : BlockContent
{
public string MyProperty2 { get; set; }
}
This code results in the following json:
{
"$type": "UserQuery+ListBlock, LINQPadQuery",
"Title": "Titel",
"Blocks": {
"$type": "System.Collections.Generic.List`1[[UserQuery+BlockContent, LINQPadQuery]], System.Private.CoreLib",
"$values": [
{
"$type": "UserQuery+InnerBlock, LINQPadQuery",
"MyProperty": "Inner Property",
"Id": 11
},
{
"$type": "UserQuery+InnerBlock2, LINQPadQuery",
"MyProperty2": "Inner property 2",
"Id": 12
}
]
},
"Id": 1
}
Using these versions at the moment:
Elasticsearch 7.4.2
Nest 7.4.2
Update:
The solution provided by Russ Cam below works like a charm for the data model included in the response, however I've put together an example below based on how we create indices (using automap) and bulk index the initial list of documents. This works fine if we exclude the list of Guids (CategoryIds) in the model but if we include it the following exception is thrown:
{
"took": 8,
"errors": true,
"items": [{
"index": {
"_index": "testindex",
"_type": "_doc",
"_id": "1",
"status": 400,
"error": {
"type": "mapper_parsing_exception",
"reason": "failed to parse field [categoryIds] of type [keyword] in document with id '1'. Preview of field's value: '{$values=[], $type=System.Collections.Generic.List`1[[System.Guid, System.Private.CoreLib]], System.Private.CoreLib}'",
"caused_by": {
"type": "illegal_state_exception",
"reason": "Can't get text on a START_OBJECT at 1:140"
}
}
}
}, {
"index": {
"_index": "testindex",
"_type": "_doc",
"_id": "2",
"status": 400,
"error": {
"type": "mapper_parsing_exception",
"reason": "failed to parse field [categoryIds] of type [keyword] in document with id '2'. Preview of field's value: '{$values=[], $type=System.Collections.Generic.List`1[[System.Guid, System.Private.CoreLib]], System.Private.CoreLib}'",
"caused_by": {
"type": "illegal_state_exception",
"reason": "Can't get text on a START_OBJECT at 1:141"
}
}
}
}
]
}
Here is a simple (.Net 5) console application where this behaviour hopefully can be reproduced by others also:
using System;
using System.Collections.Generic;
using System.Linq;
using Elasticsearch.Net;
using Nest;
using Nest.JsonNetSerializer;
using Newtonsoft.Json;
namespace ElasticsearchTypeSerializer
{
internal class Program
{
private const string IndexName = "testindex";
private static void Main(string[] args)
{
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var settings = new ConnectionSettings(pool,
(builtin, settings) => new MySecondCustomJsonNetSerializer(builtin, settings));
settings.DisableDirectStreaming();
var client = new ElasticClient(settings);
CreateIndex(client);
IndexDocuments(client);
var documents = GetDocuments(client);
}
private static void CreateIndex(IElasticClient client)
{
var createIndexResponse = client.Indices.Create(IndexName, x => x.Map<MyDocument>(m => m.AutoMap()));
}
private static void IndexDocuments(IElasticClient client)
{
var documents = new List<MyDocument>
{
new()
{
Id = 1,
Name = "My first document",
OwnerId = 2,
SubDocuments = new List<SubDocument>
{
new MySubDocument {Id = 11, Name = "my first sub document"},
new MySubDocument2 {Id = 12, Description = "my second sub document"}
}
},
new()
{
Id = 2,
Name = "My second document",
OwnerId = 3,
SubDocuments = new List<SubDocument>
{
new MySubDocument {Id = 21, Name = "My third sub document"}
}
}
};
var bulkIndexResponse = client.Bulk(b => b.Index(IndexName).IndexMany(documents).Refresh(Refresh.True));
}
private static IEnumerable<MyDocument> GetDocuments(IElasticClient client)
{
var searchResponse = client.Search<MyDocument>(s => s.Index(IndexName).Query(q => q.MatchAll()));
var documents = searchResponse.Documents.ToList();
return documents;
}
}
public class MyDocument
{
public int Id { get; set; }
public string Name { get; set; }
public string FilePath { get; set; }
public int OwnerId { get; set; }
public List<Guid> CategoryIds { get; set; } = new();
public List<SubDocument> SubDocuments { get; set; }
}
public class SubDocument
{
public int Id { get; set; }
}
public class MySubDocument : SubDocument
{
public string Name { get; set; }
}
public class MySubDocument2 : SubDocument
{
public string Description { get; set; }
}
public class MySecondCustomJsonNetSerializer : ConnectionSettingsAwareSerializerBase
{
public MySecondCustomJsonNetSerializer(IElasticsearchSerializer builtinSerializer,
IConnectionSettingsValues connectionSettings)
: base(builtinSerializer, connectionSettings)
{
}
protected override JsonSerializerSettings CreateJsonSerializerSettings()
{
return new()
{
TypeNameHandling = TypeNameHandling.All,
NullValueHandling = NullValueHandling.Ignore,
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple
};
}
}
}
Any help regarding this issue is very much appreciated!
If you want type information to be included for typed collections on a document, the derived contract resolver can be omitted, which supresses the type handling for collection item types
private static void Main()
{
var pool = new SingleNodeConnectionPool(new Uri($"http://localhost:9200"));
var settings = new ConnectionSettings(pool,
(builtin, settings) => new MySecondCustomJsonNetSerializer(builtin, settings));
var client = new ElasticClient(settings);
var document = new MyDocument
{
Id = 1,
Name = "My first document",
OwnerId = 2,
SubDocuments = new[]
{
new MySubDocument { Name = "my first sub document" },
new MySubDocument { Name = "my second sub document" },
}
};
var indexResponse = client.IndexDocument(document);
}
public class MyDocument
{
public int Id { get; set; }
public string Name { get; set; }
public string FilePath { get; set; }
public int OwnerId { get; set; }
public IEnumerable<MySubDocument> SubDocuments { get; set; }
}
public class MySubDocument
{
public string Name { get; set; }
}
public class MySecondCustomJsonNetSerializer : ConnectionSettingsAwareSerializerBase
{
public MySecondCustomJsonNetSerializer(IElasticsearchSerializer builtinSerializer, IConnectionSettingsValues connectionSettings)
: base(builtinSerializer, connectionSettings) { }
protected override JsonSerializerSettings CreateJsonSerializerSettings() =>
new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All,
NullValueHandling = NullValueHandling.Ignore,
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple
};
}
I have retrieved json data from facebook graph api requesting GET from
https://graph.facebook.com/YOUR_PAGE_ID/albums?fields=name,photos{picture}&access_token=YOUR_ACCESS_TOKEN/
Json data is as follows:
{
"data": [
{
"name": "School Kids",
"photos": {
"data": [
{
"picture": "https://scontent.xx.fbcdn.net/v/t1.0-0/s130x130/15267641_1789056354665765_6384898034258459703_n.jpg?oh=44daa7be0ac1878e769bc16df444bd0a&oe=58B29329",
"id": "1789056354665765"
},
{
"picture": "https://scontent.xx.fbcdn.net/v/t1.0-0/s130x130/15356660_1789056361332431_834718824553815513_n.jpg?oh=69b3f1b1697808b87eed1e3053a67aaf&oe=58B735FB",
"id": "1789056361332431"
},
{
"picture": "https://scontent.xx.fbcdn.net/v/t1.0-0/s130x130/15356635_1789056401332427_1231396155404307815_n.jpg?oh=3de32d320ac6762adc0dbf8b1ef64e0e&oe=58F69648",
"id": "1789056401332427"
},
{
"picture": "https://scontent.xx.fbcdn.net/v/t1.0-0/s130x130/15380574_1789337074637693_1697389498501034556_n.jpg?oh=ddd57d119882b47172af689abde20cfb&oe=58F15477",
"id": "1789337074637693"
}
],
"paging": {
"cursors": {
"before": "MTc4OTA1NjM1NDY2NTc2NQZDZD",
"after": "MTc4OTMzNzA3NDYzNzY5MwZDZD"
}
}
},
"id": "1789056284665772"
},
{
"name": "Cover Photos",
"photos": {
"data": [
{
"picture": "https://scontent.xx.fbcdn.net/v/t1.0-0/s130x130/14519945_1762073987364002_4539899568406717011_n.jpg?oh=fc8c02e9ced0839eea22d08859b964d0&oe=58BC80D4",
"id": "1762073987364002"
}
],
"paging": {
"cursors": {
"before": "MTc2MjA3Mzk4NzM2NDAwMgZDZD",
"after": "MTc2MjA3Mzk4NzM2NDAwMgZDZD"
}
}
},
"id": "1762074137363987"
},
{
"name": "Profile Pictures",
"photos": {
"data": [
{
"picture": "https://scontent.xx.fbcdn.net/v/t1.0-0/p130x130/14495327_1762072887364112_1611299743258720903_n.jpg?oh=ae87944069fd154e817468a38d9cb4a1&oe=58AE8D02",
"id": "1762072887364112"
}
],
"paging": {
"cursors": {
"before": "MTc2MjA3Mjg4NzM2NDExMgZDZD",
"after": "MTc2MjA3Mjg4NzM2NDExMgZDZD"
}
}
},
"id": "1762072884030779"
}
],
"paging": {
"cursors": {
"before": "MTc4OTA1NjI4NDY2NTc3MgZDZD",
"after": "MTc2MjA3Mjg4NDAzMDc3OQZDZD"
}
}
}
What I want from these data is to get image and album name and display in GridView in android.
My attempt parsing these data is as json object from this method
public async void downloadJsonFeedAsync(string url) {
var httpClient = new HttpClient();
Task < string > contentsTask = httpClient.GetStringAsync(url);
// await! control returns to the caller and the task continues to run on another thread
string content = await contentsTask;
Console.Out.WriteLine("Response Body: \r\n {0}", content);
//Convert string to JSON object
mObject = Newtonsoft.Json.JsonConvert.DeserializeObject < ImageGridItemRootObject > (content);
//Update listview
Activity.RunOnUiThread(() => {
mGridView.Adapter = new PhotoGalleryGridViewAdapter(this.Activity, Resource.Layout.PhotoGalleryGridItemView, mObject.data);
mProgressBar.Visibility = ViewStates.Gone;
});
}
// root object
public class ImageGridItemRootObject
{
public string name { get; set; }
public string photos { get; set; }
public List<ImageGridItem> data { get; set; }
}
//ImageGridItem
public class ImageGridItem
{
private string picture;
private string id;
public ImageGridItem():base()
{
}
public string Picture
{
get { return picture; }
set { picture = value; }
}
public string Id
{
get { return id; }
set { id = value; }
}
}
The problem is I get Picture Null. I wonder which is jSon Object and which is jSon Array so that I can format rootObject in-order to retrieve jSon Array .
Thank you in advance.
If you take the returned JSON and use Edit > Paste Special > Paste JSON as classes in Visual Studio you get the following classes:
public class Rootobject
{
public Datum[] data { get; set; }
public Paging paging { get; set; }
}
public class Paging
{
public Cursors cursors { get; set; }
}
public class Cursors
{
public string before { get; set; }
public string after { get; set; }
}
public class Datum
{
public string name { get; set; }
public Photos photos { get; set; }
public string id { get; set; }
}
public class Photos
{
public Datum1[] data { get; set; }
public Paging1 paging { get; set; }
}
public class Paging1
{
public Cursors1 cursors { get; set; }
}
public class Cursors1
{
public string before { get; set; }
public string after { get; set; }
}
public class Datum1
{
public string picture { get; set; }
public string id { get; set; }
}
It looks like your classes are not matching the JSON, which means the Deserializer does not know what to do with the key/values that do not match in your contract.
Adjust your classes to better match the classes above, and it will deserialize with the image urls.
I've been able to figure out how to call my web server, load JSON, loop through it and output values. But how can I populate this data into a cell. For example, I would like to output the 3 years returned into a table. How would I go about doing that?
async Task GetTowInfo()
{
loadingIndicator.StartAnimating ();
HttpClient client = new HttpClient ();
HttpResponseMessage response = await client.GetAsync ("myurl.com");
HttpContent content = response.Content;
var result = await content.ReadAsStringAsync ();
try
{
var parsed = JsonConvert.DeserializeObject<RootObject>(result);
foreach (var year in parsed.DATA.YEARMFG)
{
Console.WriteLine("Year: {0}", year);
}
}
catch (Exception e) {
Console.WriteLine (e);
}
loadingIndicator.StopAnimating ();
}
public class DATA
{
public List<int> YEARMFG { get; set; }
public List<string> MAKE { get; set; }
public List<string> MODEL { get; set; }
public List<string> ENGINE { get; set; }
public List<int> TOWLIMIT { get; set; }
public List<string> NOTE1 { get; set; }
public List<string> NOTE2 { get; set; }
}
public class RootObject
{
public int ROWCOUNT { get; set; }
public List<string> COLUMNS { get; set; }
public DATA DATA { get; set; }
}
<!----JSON------>
{
"ROWCOUNT": 3,
"COLUMNS": [
"YEARMFG",
"MAKE",
"MODEL",
"ENGINE",
"TOWLIMIT",
"NOTE1",
"NOTE2"
],
"DATA": {
"YEARMFG": [
2012,
2012,
2012
],
"MAKE": [
"Chevrolet/GMC",
"Chevrolet/GMC",
"Chevrolet/GMC"
],
"MODEL": [
"Avalanche 1500 4WD",
"Avalanche 1500 4WD",
"Avalanche 1500 4WD"
],
"ENGINE": [
"5.3L V-8",
"5.3L V-8",
"5.3L V-8"
],
"TOWLIMIT": [
5000,
5500,
8000
],
"NOTE1": [
"3.08 axle ratio",
"3.42 axle ratio",
"3.42 axle ratio"
],
"NOTE2": [
"",
"",
"Cooling or other accessory package required "
]
}
}
I have a web api written in aspnet webapi. The main idea is to work with inherirance. What you see here is a test project to play arround before adding this to the real project. To do that on the webconfig file i have added:
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Auto;
Here is the model
public abstract class Person
{
public string Name { get; set; }
public string DocumentNumber { get; set; }
}
public class Teacher : Person
{
public string School { get; set; }
}
public class Employee : Person
{
public string PersonnelNumber { get; set; }
}
As a result, here is a response from the server:
[
{
"$type": "JsonConverterTest.Models.Employee, JsonConverterTest",
"PersonnelNumber": "1001",
"Name": "Emp1",
"DocumentNumber": "01"
},
{
"$type": "JsonConverterTest.Models.Employee, JsonConverterTest",
"PersonnelNumber": "1002",
"Name": "Emp2",
"DocumentNumber": "02"
},
{
"$type": "JsonConverterTest.Models.Teacher, JsonConverterTest",
"Name": "Teacher1",
"DocumentNumber": "10"
}
]
I need $type for web api deserization process to create the right instance for person.
What can i do to change the $type value to a more simple one like: "Models.Employee" or "Models.Teacher" and get rid of the assembly name?
Thanks in advance.