Replace template Placeholder with Object Properties with Reflection - c#

In C#, I want to replace the string Placeholder with Object Properties using Reflection
string formula = "{\"Name\": \"{{Name}}\", \"Email\": \"{{Email}}\" }";
Student student = new Student();
student.Name = "Parker";
student.Email = "Parker#xyz.com";
student.Address = "Mark Avenue";
var result1 = GenerateJson(formula, student);
//Output : "{\"Name\": \"Parker\", \"Email\": \"Parker#xyz.com\" }"
student.Name = "Royal";
student.Email = "Royal#xyz.com";
student.Address = "Cross Lane";
var result2 = GenerateJson(formula, student);
//Output : "{\"Name\": \"Royal\", \"Email\": \"Royal#xyz.com\" }"
public string GenerateJson(string formula, Student student)
{
string result = "";
//logic for replacing the Placeholder woth object properties
return result;
}
class Student
{
public string Name { get; set; }
public string Email { get; set; }
public string Address { get; set; }
}

If you really don't want or cannot use Json.NET than you can try solution below
public string GenerateJson(string formula, Student student)
{
return Regex.Replace(formula, #"\{\{(\w+)\}\}", match => typeof(Student).GetProperty(
match.Groups[1].ToString())?.GetValue(student)?.ToString());
}

You can deserialize it to ExpandoObject (IDictionary<string,object>). Then compare property names with the known type. If there is match between Dictionary's key and student's propertyName. Replace ExpandoObject's Value with Student's property's value. After all, serialize it to json.
Here it is,
public string GenerateJson(string formula, Student student)
{
IDictionary<string, object> templateValues = JsonConvert.DeserializeObject<IDictionary<string, object>>(formula);
PropertyInfo[] sourceProperty = typeof(Student).GetProperties();
foreach (var item in sourceProperty)
{
KeyValuePair<string,object> value = templateValues.FirstOrDefault(x=> x.Key == item.Name);
if (value.Key != null)
{
templateValues[item.Name] = item.GetValue(student);
}
}
return JsonConvert.SerializeObject(templateValues);
}

It looks like the actual problem is retrieving the value of specific properties to generate an API signature. It's unclear if the signature to sign really needs to be a JSON string or not.
The easiest way is to create an anonymous type with the necessary properties and serialize it, eg :
var payload=JsonConvert.Serialize(new {student.Name,student.Email});
This is far faster than any reflection code and allocates a single extra object only. If you want to use an API with a lot of different request types, it pays to use a code generator or in C# 9, a source generator to generate such calls.
It's possible (but slow) to use reflection to retrieve specific properties, eg with :
var dict=typeof(Student).GetProperties()
.Where(prop=>myProps.Contains(prop.Name))
.ToDictionary(prop=>prop.Name,prop=>prop.GetValue(student));
var json=JsonConvert.Serialize(dict);
A JSON object is actually a dictionary, so serializing a dictionary behaves similarly to serializing an object with the same properties.
Reflection is relatively expensive though, so it's a good idea to cache the PropertyInfo objects you want and reuse them:
Dictionary<Type,PropertyInfo[]> _properties=new Dictionary<Type,PropertyInfo[]>();
...
string GenerateJson<T>(T item)
{
PropertyInfo[] props;
if (!_properties.TryGetValue(typeof(T),out props))
{
props=typeof(Student).GetProperties()
.Where(prop=>myProps.Contains(prop.Name))
.ToArray();
}
var dict=props.ToDictionary(prop=>prop.Name,prop=>prop.GetValue(item));
return JsonConvert.Serialize(dict);
}

Related

How to convert object into json array without property names

For this class
class Customer {
public string FirstName {get; set;}
public string LastName {get; set;}
}
I have collection
List<Customer> customers
When returning to browser client
return new JsonResult(new
{
data = customers
});
The client get
{"data":[{"firstName":"Johny","lastName":"Johnson"}]}
Is there some way to get
{"data":[{"Johny","Johnson"}]}
without doing foreach like
var output = new List<string[]>();
foreach (var r in customers)
{
output.Add(new string[] {
r.FirstName,
r.LastName
});
}
?
You could add another property in the Customer object,
public string[] FullName {get { return new string[]{FirstName, LastName}; } }
Decorate your Firstname and LastName properties with [JsonIgnore] so they don't get serialized.
Final product would look like so
public class Customer{
[JsonIgnore]
public string FirstName{get;set;}
[JsonIgnore]
public string LastName{get;set;}
[JsonProperty("data")]
public string[] FullName {get { return new string[]{FirstName, LastName}; } }
public Customer(string FirstName, string LastName){
this.FirstName = FirstName;
this.LastName = LastName;
}
}
public static void Main(string[] args)
{
Customer c = new Customer("Adrian", "i6");
Console.Write(JsonConvert.SerializeObject(c));
}
This of course won't return exactly the desired result, if you wanted to completely remove the property you'll have to override the JsonWrite method inside JsonConverter however that would cause the JSON to be invalid of course as the JSON object requires a key:value property.
DotNetFiddle runnable of the above.
If you want some sort of "automatically derive a table from an array of JSON objects" functionality that's general across any data type, the algorithm would be to:
Iterate over the array, collecting newly-encountered property names into a list as you go into column names. This is the only way to get all property names since it may not be guaranteed that all objects have the same properties in JSON.
Create a list for each object in the list
Map each object's property value into the list index of the column corresponding to the property name
This will give you two output artifacts: the column listings and the values by index. If you are safe assuming that the first object has the same properties as all the other objects in the Array, then you can avoid iterating over the entire collection for the first step. This is untested, but hopefully you get the gist.
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
// ...
var payload = JObject.Parse(rawJson);
var dataArray = payload["data"] as JArray;
var firstItem = dataArray[0] as JObject;
var columns = firstItem.Properties().Select(prop => prop.Name).ToList();
var rows = (
from obj as JObject in dataArray
select columns.Select(col => obj[col]).ToList()
).ToList();

Passing c# object as query string

I want to pass C# object as query string & i used following code to get the desired result.
class Program
{
public static string GetQueryString(object obj)
{
var properties = from p in obj.GetType().GetProperties()
where p.GetValue(obj, null) != null
select p.Name + "=" + HttpUtility.UrlEncode(p.GetValue(obj, null).ToString());
return String.Join("&", properties.ToArray());
}
static void Main(string[] args)
{
Filters fil = new Filters();
fil.Age = 10;
fil.Id = "some id";
fil.Divisions = new List<string>();
fil.Divisions.Add("div 1");
fil.Divisions.Add("div 2");
fil.Divisions.Add("div 3");
fil.Names = new List<string>();
fil.Names.Add("name 1");
fil.Names.Add("name 2");
fil.Names.Add("name 3");
var queryStr = GetQueryString(fil);
Console.ReadKey();
}
}
public class Filters
{
public List<string> Names { get; set; }
public List<string> Divisions { get; set; }
public int Age { get; set; }
public string Id { get; set; }
}
using the above code give me following result:
Names=System.Collections.Generic.List%601%5bSystem.String%5d&Divisions=System.Collections.Generic.List%601%5bSystem.String%5d&Age=10&Id=some+id
The output is not a valid query string. I need help to convert any POCO class into query string format.
I have a similar JavaScript object and i am able to convert it into correct query string.
{
"id":"some id",
"age":10,
"division":["div 1","div 2","div 3"],
"names":["name 1","name 2","name 3"]
}
using Jquery I can say $.param(obj) and this will result in:
"id=some+id&age=10&division%5B%5D=div+1&division%5B%5D=div+2&division%5B%5D=div+3&names%5B%5D=name+1&names%5B%5D=name+2&names%5B%5D=name+3"
I want a similar output using c#.
It looks like The problem is that you are calling ToString() on your objects. List<String>.ToString() will return "List<System.String>", which is what you're seeing, except URL encoded.
You will need to either:
Provide an iterface with a ToQueryString method:
public interface IQueryStringable
{
string ToQueryString();
}
and have all classes you might want to use as query strings implement it, or
Rewrite your reflection so that it iterates sequences. Something like (pseudocode):
Get property.
See if it is an instance of IEnumerable. If not, proceed as before
Otherwise:
for each item, construct a string consisting of the property name, "[]=" and the value of that item.
Concatenate the produced strings and urlencode it.
For sanity's sake, I would recommend option 1, and I enjoy playing with reflection. It gets more complex if you want to allow arbitrary nesting of classes.

When merging objects using Newtonsoft.Json, how do you ignore empty string values?

I have a data model that is defined as a class in C#. I need to merge two objects using JObject.Merge, but in the case of one particular property I need to ignore empty string values from the 2nd object, similar to how you would ignore null values. Is there an existing attribute property for this, or do I somehow need to write my own?
void Main()
{
string json1 = #"{ Foo: ""foo1"", Bar: ""bar1"" }";
string json2 = #"{ Foo: ""foo2"", Bar: null }";
string json3 = #"{ Foo: ""foo3"", Bar: """" }";
var model1 = JObject.Parse(json1);
var model2 = JObject.Parse(json2);
model1.Merge(model2);
model1.Dump();
model1 = JObject.Parse(json1);
model2 = JObject.Parse(json3);
model1.Merge(model2);
model1.Dump();
}
public class Model
{
[JsonProperty("foo")]
public string Foo { get ; set; }
[JsonProperty("bar", NullValueHandling = NullValueHandling.Ignore)]
public string Bar { get ; set; }
}
Output (1): Model.Bar = "bar1"
Output (2): Model.Bar = "";
Desired output of (2): Model.Bar = "bar1"
EDIT: OK, I realized that attributes could not be applied since I needed to work with the raw json string only as input. This was mainly due to the fact that my classes had attributes with default values. Merging classes with empty values would trigger the defaults and end up overwriting the original, which is what I didn't want. I need to be able to take a partial json representation of a class and update the original. Sorry if that wasn't clear from the get-go. I'll update what I ended up doing an answer.
You could remove the properties with empty string values from the JObject you want to merge in, using the following extension methods:
public static class JsonExtensions
{
public static void RemovePropertiesByValue(this JToken root, Predicate<JValue> filter)
{
var nulls = root.DescendantsAndSelf().OfType<JValue>().Where(v => v.Parent is JProperty && filter(v)).ToList();
foreach (var value in nulls)
{
var parent = (JProperty)value.Parent;
parent.Remove();
}
}
public static IEnumerable<JToken> DescendantsAndSelf(this JToken node)
{
if (node == null)
return Enumerable.Empty<JToken>();
var container = node as JContainer;
if (container != null)
return container.DescendantsAndSelf();
else
return new [] { node };
}
}
Then use it like:
model2.RemovePropertiesByValue(v => v.Type == JTokenType.String && string.IsNullOrEmpty((string)v.Value));
Note this doesn't remove empty strings from arrays because that would mess up array indexing, and therefore merging. Do you need that also?
I manipulated the JObject dictionary keys to massage the specific entry. I feel dirty, but it works. I can't think of a "good" way to do this.
model1 = JObject.Parse(json1);
model2 = JObject.Parse(json3);
IDictionary<string, JToken> dictionary = model2;
dictionary["Bar"] = string.IsNullOrEmpty((string)dictionary["Bar"])
? null
: dictionary["Bar"];
model1.Merge(model2);

Keep old values if not present in the json when deserialize

I have such a class:
public class item
{
public string Name { get; set; }
public string City { get; set; }
public string Pw { get; set; }
}
from which I create several objects I store in the DB. Then I want to update one of them with data coming from client in the form of a json like this:
{
"Name":"John",
"City":"NYC"
}
the idea would be to use:
item myitem = JsonConvert.DeserializeObject<item>(jsoncomingfromclient);
but doing so Pw is overwritten with null (while obviously I want to keep the original value)
NullValueHandling looks like a good candidate but it works if the value is null, in my case it is completely missing from the json.
Any idea how to deserialize a json keeping the old value in the destination object if the value is missing in the json?
Use JsonConvert.PopulateObject. It's designed for this purpose:
var item = new item { Name = "my name", City = "my city", Pw = "my pw" };
var json = #"
{
""Name"":""John"",
""City"":""NYC""
}";
JsonConvert.PopulateObject(json, item);
Debug.Assert(item.Pw == "my pw"); // no assert
Debug.Assert(item.Name == "John"); // no assert
Debug.Assert(item.City == "NYC"); // no assert
This part of code JsonConvert.DeserializeObject<item>(jsoncomingfromclient); will create new instance of type item based on parameter jsoncomingfromclient and return it.
This part item myitem = ... declares a variable myitem of type item and gives it a concrete instance. So there is no way to merge anything like this.
You just have to write some merge method manually and define what and how is merged between two objects.
Something like that
item dbitem = ...
item myitem = JsonConvert.DeserializeObject<item>(jsoncomingfromclient);
item mergedItem = myitem.merge(dbitem)

How to extract an object name

i've got a class filled with lists of subclasses:
public class ClassOfKb
{
public List<Data> KbDatas {get;set;}
public List<Product> KbProducts {get;set}
}
public class Data
{
public Guid ID {get;set;}
public byte[] data {get;set;}
public string Name {get;set;}
}
public class Product
{
public Guid ID {get;set;}
public string Name {get;set;}
public byte[] Image {get;set;}
}
i create an object:
ClassOfKb kb = new ClassOfKb
now i'd like to extract the string "Datas" from the sub-object kb.KbDatas, I tried:
string name = kb.KbDatas.GetType().BaseType.Name.Substring(2);
aswell as:
string name = kb.KbDatas.GetType().Name.Substring(2);
but nothing gave me what I need, is there any way to do this?
EDIT: to specify my question, the string I need is the name of the list, except the first two letters! KbDatas => Datas
EDIT2: i did a mistake, the list-names and class-names are different and i need the list-name
You can use Type.GetGenericArguments to solve this
ClassOfKb kb=new ClassOfKb();
kb.KbData = new List<Data>();
string nameOfData = Type.GetType(kb.KbData.ToString()).GetGenericArguments().Single().Name;
OUTPUT : nameOfData = Data
kb.KbProduct = new List<Product>();
string nameOfProduct = Type.GetType(kb.KbProduct.ToString()).GetGenericArguments().Single().Name;
OUTPUT : nameOfProduct = Product
Since that's a collection it is likely that there are multiple Data objects in it, each with a name. You can use String.Join to concat them with a separator:
string names = string.Join(",", kb.KbData.Select(d => d.Name));
If there's just one object you don't get a comma at the end. If there's no object you get an empty string.
erm, since you have a List of Data there will be a sequence of Names.
IEnumerable<string> names = kb.KbData.Select(d => d.Name);
maybe you want just the first one?
string firstName = kb.KbData.First(d => d.Name);
Try this one
string name = kb.KbData[0].Name.Substring(2);
From the sounds of what you've written, you're looking to get the name of the type in the List instance KbData?
If so, I think this may be what you're looking for: https://stackoverflow.com/a/1043778/775479
If you are trying to get the name of the property. There are several methods for doing so.
Get the name of the generic argument from the property itself - If you know the name of the property.
ClassOfKb kb = new ClassOfKb()
{ KbData = new List<Data>(), KbProduct = new List<Product>() };
Console.WriteLine(kb.KbData.GetType().GetGenericArguments()[0].Name);
Get the name of the property from reflection, if you know the data type of the property.
System.Reflection.PropertyInfo pi = kb.GetType()
.GetProperties()
.FirstOrDefault(p=>p.PropertyType == typeof(List<Data>));
Console.WriteLine(pi.Name.Substring(2)); // ignoring the kb prefix
You can achieve this with reflection. This is example without any checks - just show the mechanism:
PropertyInfo propertyInfo = typeof(ClassOfKb).GetProperty("KbData");
Type propertyType = propertyInfo.PropertyType;
Type genericArgument = propertyType.GenericTypeArguments[0];
string name = genericArgument.Name;
Because property KbData is generic List<Data> you need ask for generic arguments of property type: propertyType.GenericTypeArguments[0] and you should test if the type is really generic by genericArgument.IsGenericType and check generic arguments count
If you need the property name than you can use Expression.
The code below define function for extract name prom a property:
public string GetPropertyName<T>(Expression<Func<T>> property)
{
return ((MemberExpression)property.Body).Member.Name;
}
This converts property to property name string:
GetPropertyName(()=>k.KbDatas).Substring(2)

Categories