Dynamically deserializing to a property in RestSharp - c#

I am playing with the Harvest API and I'm trying to automatically map the entities as easy as possible, unfortunately when I do a request like GET /projects it generates a result like so:
[{
project: {
name: "Test"
}
},
{
project: {
name: "Test 2"
}]
In RestSharp, I can't directly do this:
client.Execute<List<Project>>(request)
Because it is going to look for a property called Project. So I have to make another class that has that property, and call it like this:
client.Execute<List<ProjectContainer>>(request)
I don't want to make a 'container' class for every entity, so I thought I found a clever solution to make one class I can use on all:
public class ListContainer<T> where T : IHarvestEntity
{
public T Item { get; set; }
}
But, of course, the deserializer has no idea it needs to map the entity name (or "Project") to the property Item. In the restsharp documentation I found that I could use [DeserializeAs(Name = "CustomProperty")] to tell the deserializer which field to map to this property. However, attributes do only allow constants, which means I can't do:
[DeserializeAs(Name = typeof(T).FullName)]
public T Item { get; set; }
Does anyone know a clever solution to this? So i don't have to create 10 different container classes?

I suggest you use the XPath equivalent for Json. With Json.NET you can parse the string and create a dynamic object.
With SelectToken you can query values, or using Linq.
The code looks something like this (I did not test it):
// execute the request
RestResponse response = client.Execute(request);
var content = response.Content; // raw content as string
JObject o = JObject.Parse(content);
IList<string> projectNames = o.SelectToken("project").Select(s => (string)s.name).ToList();
You can code the paths or configure the paths anyway you like.
--- Edit ---
Here's an example that I tested, converting the json string to a list of projects.
var projects = JArray.Parse(response.Content).Select(r => new Project(r["project"]["name"].Value<string>())).ToList();

To keep it really simple, you can use List<dynamic> and access the property/properties by name with a one-liner.
var names = client.Execute<List<dynamic>>(request).Data.Select(
item => item["project"]["name"]).ToList(); // list of names
If this is not sufficient, then you could improvise your own mapper and extract a collection of e.g. Project instances:
var projects = client.Execute<List<dynamic>>(request).Data.Select(
item => Map<Project>(item)).ToList(); // list of Project instances
where Map method could be something like
public T Map<T>(dynamic item) where T : class
{
// inline for clarity
var mappings = new Dictionary<Type,Func<dynamic,object>>
{
{ typeof(Project), map => new Project(map["project"]["name"]) }
};
return (T)mappings[typeof(T)].Invoke(item);
}
given Project is defined as
public class Project
{
public Project(string name)
{
Name = name;
}
public string Name { get; set; }
}

Related

Create a JSON object using properties of a C# class

I'm trying to construct a request body for a REST api call, and I need to create a JSON object with the list of properties I want to get back.
For eg: I have this C# object that I want to get back:
public class SomeProperties
{
public string TicketNumber { get; set; }
public Driver Driver { get; set; }
}
public class Driver
{
public string Name { get; set; }
}
To get this back, I need to put these properties in a JSON request body like this:
"properties": [
"ticketNumber",
"driver.name"
]
My attempt looks like this:
private string FetchProperties()
{
var fetchProperties = new
{
properties = new List<string>
{
"ticketNumber",
"driver.name"
}
};
var jsonResult = JsonConvert.SerializeObject(fetchProperties, Formatting.None);
return jsonResult;
}
But I don't want to hard code the properties like that.
So is there any way I can use property names from the object I want, to put in the list of strings that I made in the method above?
Thank You!
If I understand correctly,you need Metadata of model.
if you use EntityFramework, you can get metadata of your model
from this Code
and call BuildJsonMetadata() function
and if you use other mapper, I dont see any exist tool for generate metadata of model and you must generate it handly
somthing like this
First of, if you serialize the class you have (SomeProperties), you will not get driver.name. Instead you will get a string like this one that shows driver as an object,
{
properties : {
"ticketNumber" : "stringvalue",
"driver" : {
"name" : "stringValue"
}
}
}
That said, if you are interested in getting a json like this,
"properties": [
"ticketNumber",
"driver.name"
]
you will need a class (very simple one at that) that contains only a list of strings. properties is not an array of objects, but simply strings. From the looks of the FetchProperties method, you are creating an object with fetchProperties as the RootObject. Try something like this,
public class MyClass
{
[JsonProperty("fetchProperties")]
public Fetch FetchProperties { get; set; }
}
public class Fetch
{
[JsonProperty("properties")]
public List<string> Properties { get; set; }
}
private string FetchProperties()
{
MyClass obj = new MyClass()
{
FetchProperties = new Fetch()
{
Properties = new List<string>() { "ticketNumber", "driver.Name" }
}
};
return JsonConvert.SerializeObject(obj); // Formatting.None is by default
}
Now its your choice to hard code these values or, pass them as arguments or use a local variable that contains a list of all the strings you intend to store as "properties". You cant use enums because of violation in naming convention (driver.name) so these options should suffice.

Create json in UWP without JSON.Net

I am trying to create a json formatted string using c# in UWP without JSON.Net, but I am just not understanding how to get there. Let's say I wanted to create the following json dynamically:
[{"id":130},{"id":131},{"id":132},{"id":133},{"id":134}]
From everything I have read, it would seem that I need a class that defines the content of my json. For example:
class accountTypes
{
public int id { get; set; }
public string type { get; set; }
}
From there, it would seem that I only need to create a list of type "accountTypes" and then add each "id" to the list.
List<accountTypes> jsonList = new List<accountTypes>();
int numOfChildren = AccountTypesList.Children.Count;
for (int i = 0; i < numOfChildren; i++)
{
if (((CheckBox)AccountTypesList.Children[i]).IsChecked == true)
{
jsonList.Add(new accountTypes() { id = (int)(double)((CheckBox)AccountTypesList.Children[i]).Tag });
}
}
While I am 99% sure that the above code is very flawed, it does not crash on me, so that is a start at least. What I am struggling with now though is how I would serialize the list "jsonList". Everything I have read thus far either points to JSON.net or the JavaScriptSerializer Class, and not Windows.Data.Json. If I could see a simple example on how to serialize json using Windows.Data.Json, then I could at least visualize what is going on with my list and could correct it accordingly. That being said, how do I serialize an array or a list using Windows.Data.Json?
First of all, there's no built-in JSON-serializer that handles all the mapping for you. This is exactly what JSON.NET is doing for you. Therefore, you have to take the manual and long way.
To create exactly this result:
[{"id":130},{"id":131},{"id":132},{"id":133},{"id":134}]
You have to use the JsonArray class. For example, pass your jsonList object to a method like this:
public string ToJson(List<accountTypes> objectList)
{
var jArray = new JsonArray();
foreach (var at in objectList)
{
jArray.Add(ToJson(at));
}
return jArray.ToString();
}
Whereas you use this method to create a JsonObject for your class object itself (as manual step as well):
public JsonObject ToJson(accountTypes at)
{
var jObj = new JsonObject();
jObj.SetNamedValue("id", JsonValue.CreateNumberValue(at.id));
return jObj;
}

MongoDB Linq OfType() on fields

This is my MongoDB document structure:
{
string _id;
ObservableCollection<DataElement> PartData;
ObservableCollection<DataElement> SensorData;
...
other ObservableCollection<DataElement> fields
...
other types and fields
...
}
Is there any possibility to retrieve a concatenation of fields with the type ObservableCollection<DataElement>? Using LINQ I would do something like
var query = dbCollection
.AsQueryable()
.Select(x => new {
data = x
.OfType(typeof(ObservableCollection<DataElement>))
.SelectMany(x => x)
.ToList()
});
or alternatively
data = x.Where(y => typeof(y) == typeof(ObservableCollection<DataElement>)
.SelectMany(x => x).ToList()
Unfortunately .Where() and .OfType() do not work on documents, only on queryables/lists, so is there another possibility to achieve this? The document structure must stay the same.
Edit:
After dnickless answer I tried it with method 1b), which works pretty well for getting the fields thy way they are in the collection. Thank you!
Unfortunately it wasn't precisely what I was looking for, as I wanted to be all those fields with that specific type put together in one List, at it would be returned by the OfType or Where(typeof) statement.
e.g. data = [x.PartData , x.SensorData, ...] with data being an ObsverableCollection<DataElement>[], so that I can use SelectMany() on that to finally get the concatenation of all sequences.
Sorry for asking the question unprecisely and not including the last step of doing a SelectMany()/Concat()
Finally I found a solution doing this, but it doesn't seem very elegant to me, as it needs one concat() for every element (and I have more of them) and it needs to make a new collection when finding a non-existing field:
query.Select(x => new
{
part = x.PartData ?? new ObservableCollection<DataElement>(),
sensor = x.SensorData ?? new ObservableCollection<DataElement>(),
}
)
.Select(x => new
{
dataElements = x.part.Concat(x.sensor)
}
).ToList()
In order to limit the fields returned you would need to use the MongoDB Projection feature in one way or the other.
There's a few alternatives depending on your specific requirements that I can think of:
Option 1a (fairly static approach): Create a custom type with only the fields that you are interested in if you know them upfront. Something like this:
public class OnlyWhatWeAreInterestedIn
{
public ObservableCollection<DataElement> PartData { get; set; }
public ObservableCollection<DataElement> SensorData { get; set; }
// ...
}
Then you can query your Collection like that:
var collection = new MongoClient().GetDatabase("test").GetCollection<OnlyWhatWeAreInterestedIn>("test");
var result = collection.Find(FilterDefinition<OnlyWhatWeAreInterestedIn>.Empty);
Using this approach you get a nicely typed result back without the need for custom projections.
Option 1b (still pretty static): A minor variation of Option 1a, just without a new explicit type but a projection stage instead to limit the returned fields. Kind of like that:
var collection = new MongoClient().GetDatabase("test").GetCollection<Test>("test");
var result = collection.Find(FilterDefinition<Test>.Empty).Project(t => new { t.PartData, t.SensorData }).ToList();
Again, you get a nicely typed C# entity back that you can continue to operate on.
Option 2: Use some dark reflection magic in order to dynamically create a projection stage. Downside: You won't get a typed instance reflecting your properties but instead a BsonDocument so you will have to deal with that afterwards. Also, if you have any custom MongoDB mappings in place, you would need to add some code to deal with them.
Here's the full example code:
First, your entities:
public class Test
{
string _id;
public ObservableCollection<DataElement> PartData { get; set; }
public ObservableCollection<DataElement> SensorData { get; set; }
// just to have one additional property that will not be part of the returned document
public string TestString { get; set; }
}
public class DataElement
{
}
And then the test program:
public class Program
{
static void Main(string[] args)
{
var collection = new MongoClient().GetDatabase("test").GetCollection<Test>("test");
// insert test record
collection.InsertOne(
new Test
{
PartData = new ObservableCollection<DataElement>(
new ObservableCollection<DataElement>
{
new DataElement(),
new DataElement()
}),
SensorData = new ObservableCollection<DataElement>(
new ObservableCollection<DataElement>
{
new DataElement(),
new DataElement()
}),
TestString = "SomeString"
});
// here, we use reflection to find the relevant properties
var allPropertiesThatWeAreLookingFor = typeof(Test).GetProperties().Where(p => typeof(ObservableCollection<DataElement>).IsAssignableFrom(p.PropertyType));
// create a string of all properties that we are interested in with a ":1" appended so MongoDB will return these fields only
// in our example, this will look like
// "PartData:1,SensorData:1"
var mongoDbProjection = string.Join(",", allPropertiesThatWeAreLookingFor.Select(p => $"{p.Name}:1"));
// we do not want MongoDB to return the _id field because it's not of the selected type but would be returned by default otherwise
mongoDbProjection += ",_id:0";
var result = collection.Find(FilterDefinition<Test>.Empty).Project($"{{{mongoDbProjection}}}").ToList();
Console.ReadLine();
}
}

How can I loop through the JSON on Universal Windows Platform with C#

I am developing an app on UWP.
When I connect with a server api and I get the next response I don't have problems.
{"value":"Login successfull","sessionId":"a95077855b05ed0fec5d7fa3abafa126e15aba2a"}
I can get information in the following way:
JsonObject jsonObject = JsonObject.Parse(jsonString);
string token = jsonObject["sessionId"].GetString();
string value = jsonObject["value"].GetString();
but my problem is when i get the next response of the api:
[{"person":{"name":"name1","country":"Spain","city":"user_city","phone":null}},{"person":{"name":"name2","country":"Turkey","city":"user_city","phone":"1111111"}},{"person":{"name":"name3","country":"Argentina","city":"user_city","phone":"22222"}},{"person":{"name":"name4","country":"Argentina","city":"user_city","phone":"33333"}}]
How can I loop through the JSON and get all the people that match a condition?
I have to do with "Windows.Data.Json"
If interested in a solution using only Windows.Data.Json namespace, here it is:
var rootValue = JsonValue.Parse(jsonString);
foreach (var item in rootValue.GetArray())
{
var unamedObject = item.GetObject();
var personObject = unamedObject["person"].GetObject();
System.Diagnostics.Debug.WriteLine(personObject["name"].GetString());
System.Diagnostics.Debug.WriteLine(personObject["country"].GetString());
System.Diagnostics.Debug.WriteLine(personObject["city"].GetString());
System.Diagnostics.Debug.WriteLine(personObject["phone"].GetString());
}
Why would somebody pick Windows.Data.Json over Newtonsoft's Json.net?
If your JSON needs are simple, you can reduce the size of your app ~1 MB by choosing Windows.Data.Json because it is part of the operating system.
I would recommend you try out Json.net nuget package and deserialise the json payload to classes through that.
A good tutorial can be found here: http://windowsapptutorials.com/windows-phone/general/deserialize-json-data-using-newtonsoft-json-net-library/
But if you search you'll find more.
In short, you first copy paste your json and use Visual Studio > File > Paste Special > To paste to classes ( first open an empty cs file and set your cursor inside it ).
After that you use JsonConvert.DeserializeObject<RootObject>() to actually parse the json string.
Once parsed you'll have an array of items if your original json also defined an array.
Note RootObject is the first class object in the generated classes in Visual Studio
There are ways to do it without external libraries, if that is the real reason for the stipulation of Windows.Data.Json.
I'd likely do it something like this...
First I'd make some classes representing the returning JSON:
public class RootObject
{
public Person person { get; set; }
}
public class Person
{
public string name { get; set; }
public string country { get; set; }
public string city { get; set; }
public string phone { get; set; }
}
Then add a little method to deserialize:
public static T Deserialize<T>(string json)
{
var bytes = Encoding.Unicode.GetBytes(json);
using (var ms = new MemoryStream(bytes))
{
var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(T));
return (T)serializer.ReadObject(ms);
}
}
And finally deserialize and query that result like so:
var persons = Json.Deserialize<List<RootObject>>(textBox.Text);
var peeps = (from p in persons
where p.person.name.StartsWith("name")
select p).ToList();

Mapping JSON (jQuery) to KeyValuePair

I have a JSON object with numerous properties that I am submitting to a C# web service via jQuery.ajax() - it looks something like this:
var obj = {};
obj.LanguageCode = 1031;
obj.Gender = { 'Geschlecht': 'Mann' };
obj.City = { 'Stadt': 'Berlin' };
...
Some properties, like Gender and City, store a localized/translated prompt and response that I want to map to a KeyValuePair. I've tried formatting the Javascript in different ways, but the data only comes through when the datatype is Dictionary - when the datatype is KeyValuePair it doesn't work. For example:
private Dictionary Gender { get; set; } // works: Gender[0] == {[Geschlecht,Mann]}
private KeyValuePair City { get; set; } // doesn't work: City == {[,]}
I can use Dictionary if necessary since it works, but it seems like KeyValuePair is more appropriate and cleaner to use. Can you map Javascript objects to KeyValuePairs, or am I stuck with using Dictionary?
Looks like you need a collection of KeyValuePair objects, not a single one (even though your collection would only have one item) - that's all a Dictionary is, a collection with a few helpers around it.
But personally, I'd recommend building an actual class to represent your values, to organize it a little better - a little more verbose, but I think it's worth it.
// C#
public class LocalAndTranslated {
public string Localized { get;set; }
public string Translated { get;set; }
}
// JS
obj.Gender = { Localized: "Geschlecht", Translated: "Man" };
If you wanted to, you can even go so far as to define a "class" in javascript:
var LocalAndTranslated = (function() {
function LocalAndTranslated(localized, translated) {
this.Localized = localized;
this.Translated = translated;
}
return LocalAndTranslated;
})();
obj.Gender = new LocalAndTranslated("Geschlecht", "Man");

Categories

Resources