I'm preparing a migration from ASP.NET Core 2.2 to 3.0.
As I don't use more advanced JSON features (but maybe one as described below), and 3.0 now comes with a built-in namespace/classes for JSON, System.Text.Json, I decided to see if I could drop the previous default Newtonsoft.Json.
Do note, I'm aware that System.Text.Json will not completely replace Newtonsoft.Json.
I managed to do that everywhere, e.g.
var obj = JsonSerializer.Parse<T>(jsonstring);
var jsonstring = JsonSerializer.ToString(obj);
but in one place, where I populate an existing object.
With Newtonsoft.Json one can do
JsonConvert.PopulateObject(jsonstring, obj);
The built-in System.Text.Json namespace has some additional classes, like JsonDocumnet, JsonElement and Utf8JsonReader, though I can't find any that take an existing object as a parameter.
Nor am I experienced enough to see how to make use of the existing one's.
There might be a possible upcoming feature in .Net Core (thanks to Mustafa Gursel for the link), but meanwhile (and what if it doesn't),...
...I now wonder, is it possible to achieve something similar as what one can do with PopulateObject?
I mean, is it possible with any of the other System.Text.Json classes to accomplish the same, and update/replace only the properties set?,... or some other clever workaround?
Here is a sample input/output of what I am looking for, and it need to be generic as the object passed into the deserialization method is of type <T>). I have 2 Json string's to be parsed into an object, where the first have some default properties set, and the second some, e.g.
Note, a property value can be of any other type than a string.
Json string 1:
{
"Title": "Startpage",
"Link": "/index",
}
Json string 2:
{
"Head": "Latest news"
"Link": "/news"
}
Using the 2 Json strings above, I want an object resulting in:
{
"Title": "Startpage",
"Head": "Latest news",
"Link": "/news"
}
As seen in above sample, if properties in the 2nd has values/is set, it replace values in the 1st (as with "Head" and "Link"), if not, existing value persist (as with "Title")
So assuming that Core 3 doesn't support this out of the box, let's try to work around this thing. So, what's our problem?
We want a method that overwrites some properties of an existing object with the ones from a json string. So our method will have a signature of:
void PopulateObject<T>(T target, string jsonSource) where T : class
We don't really want any custom parsing as it's cumbersome, so we'll try the obvious approach - deserialize jsonSource and copy the result properties into our object. We cannot, however, just go
T updateObject = JsonSerializer.Parse<T>(jsonSource);
CopyUpdatedProperties(target, updateObject);
That's because for a type
class Example
{
int Id { get; set; }
int Value { get; set; }
}
and a JSON
{
"Id": 42
}
we will get updateObject.Value == 0. Now we don't know if 0 is the new updated value or if it just wasn't updated, so we need to know exactly which properties jsonSource contains.
Fortunately, the System.Text.Json API allows us to examine the structure of the parsed JSON.
using var json = JsonDocument.Parse(jsonSource).RootElement;
We can now enumerate over all properties and copy them.
foreach (var property in json.EnumerateObject())
{
OverwriteProperty(target, property);
}
We will copy the value using reflection:
void OverwriteProperty<T>(T target, JsonProperty updatedProperty) where T : class
{
var propertyInfo = typeof(T).GetProperty(updatedProperty.Name);
if (propertyInfo == null)
{
return;
}
var propertyType = propertyInfo.PropertyType;
v̶a̶r̶ ̶p̶a̶r̶s̶e̶d̶V̶a̶l̶u̶e̶ ̶=̶ ̶J̶s̶o̶n̶S̶e̶r̶i̶a̶l̶i̶z̶e̶r̶.̶P̶a̶r̶s̶e̶(̶u̶p̶d̶a̶t̶e̶d̶P̶r̶o̶p̶e̶r̶t̶y̶.̶V̶a̶l̶u̶e̶,̶ ̶p̶r̶o̶p̶e̶r̶t̶y̶T̶y̶p̶e̶)̶;̶
var parsedValue = JsonSerializer.Deserialize(
updatedProperty.Value.GetRawText(),
propertyType);
propertyInfo.SetValue(target, parsedValue);
}
We can see here that what we're doing is a shallow update. If the object contains another complex object as its property, that one will be copied and overwritten as a whole, not updated. If you require deep updates, this method needs to be changed to extract the current value of the property and then call the PopulateObject recursively if the property's type is a reference type (that will also require accepting Type as a parameter in PopulateObject).
Joining it all together we get:
void PopulateObject<T>(T target, string jsonSource) where T : class
{
using var json = JsonDocument.Parse(jsonSource).RootElement;
foreach (var property in json.EnumerateObject())
{
OverwriteProperty(target, property);
}
}
void OverwriteProperty<T>(T target, JsonProperty updatedProperty) where T : class
{
var propertyInfo = typeof(T).GetProperty(updatedProperty.Name);
if (propertyInfo == null)
{
return;
}
var propertyType = propertyInfo.PropertyType;
v̶a̶r̶ ̶p̶a̶r̶s̶e̶d̶V̶a̶l̶u̶e̶ ̶=̶ ̶J̶s̶o̶n̶S̶e̶r̶i̶a̶l̶i̶z̶e̶r̶.̶P̶a̶r̶s̶e̶(̶u̶p̶d̶a̶t̶e̶d̶P̶r̶o̶p̶e̶r̶t̶y̶.̶V̶a̶l̶u̶e̶,̶ ̶p̶r̶o̶p̶e̶r̶t̶y̶T̶y̶p̶e̶)̶;̶
var parsedValue = JsonSerializer.Deserialize(
updatedProperty.Value.GetRawText(),
propertyType);
propertyInfo.SetValue(target, parsedValue);
}
How robust is this? Well, it certainly won't do anything sensible for a JSON array, but I'm not sure how you'd expect a PopulateObject method to work on an array to begin with. I don't know how it compares in performance to the Json.Net version, you'd have to test that by yourself. It also silently ignores properties that are not in the target type, by design. I thought it was the most sensible approach, but you might think otherwise, in that case the property null-check has to be replaced with an exception throw.
EDIT:
I went ahead and implemented a deep copy:
void PopulateObject<T>(T target, string jsonSource) where T : class =>
PopulateObject(target, jsonSource, typeof(T));
void OverwriteProperty<T>(T target, JsonProperty updatedProperty) where T : class =>
OverwriteProperty(target, updatedProperty, typeof(T));
void PopulateObject(object target, string jsonSource, Type type)
{
using var json = JsonDocument.Parse(jsonSource).RootElement;
foreach (var property in json.EnumerateObject())
{
OverwriteProperty(target, property, type);
}
}
void OverwriteProperty(object target, JsonProperty updatedProperty, Type type)
{
var propertyInfo = type.GetProperty(updatedProperty.Name);
if (propertyInfo == null)
{
return;
}
var propertyType = propertyInfo.PropertyType;
object parsedValue;
if (propertyType.IsValueType || propertyType == typeof(string))
{
̶p̶a̶r̶s̶e̶d̶V̶a̶l̶u̶e̶ ̶=̶ ̶J̶s̶o̶n̶S̶e̶r̶i̶a̶l̶i̶z̶e̶r̶.̶P̶a̶r̶s̶e̶(̶u̶p̶d̶a̶t̶e̶d̶P̶r̶o̶p̶e̶r̶t̶y̶.̶V̶a̶l̶u̶e̶,̶ ̶p̶r̶o̶p̶e̶r̶t̶y̶T̶y̶p̶e̶)̶;̶
parsedValue = JsonSerializer.Deserialize(
updatedProperty.Value.GetRawText(),
propertyType);
}
else
{
parsedValue = propertyInfo.GetValue(target);
P̶o̶p̶u̶l̶a̶t̶e̶O̶b̶j̶e̶c̶t̶(̶p̶a̶r̶s̶e̶d̶V̶a̶l̶u̶e̶,̶ ̶u̶p̶d̶a̶t̶e̶d̶P̶r̶o̶p̶e̶r̶t̶y̶.̶V̶a̶l̶u̶e̶,̶ ̶p̶r̶o̶p̶e̶r̶t̶y̶T̶y̶p̶e̶)̶;̶
PopulateObject(
parsedValue,
updatedProperty.Value.GetRawText(),
propertyType);
}
propertyInfo.SetValue(target, parsedValue);
}
To make this more robust you'd either have to have a separate PopulateObjectDeep method or pass PopulateObjectOptions or something similar with a deep/shallow flag.
EDIT 2:
The point of deep-copying is so that if we have an object
{
"Id": 42,
"Child":
{
"Id": 43,
"Value": 32
},
"Value": 128
}
and populate it with
{
"Child":
{
"Value": 64
}
}
we'd get
{
"Id": 42,
"Child":
{
"Id": 43,
"Value": 64
},
"Value": 128
}
In case of a shallow copy we'd get Id = 0 in the copied child.
EDIT 3:
As #ldam pointed out, this no longer works in stable .NET Core 3.0, because the API was changed. The Parse method is now Deserialize and you have to dig deeper to get to a JsonElement's value. There is an active issue in the corefx repo to allow direct deserialization of a JsonElement. Right now the closest solution is to use GetRawText(). I went ahead and edited the code above to work, leaving the old version struck-through.
Here is some sample code that does it. It's using the new Utf8JsonReader struct so it populates the object at the same time it parses it. It supports JSON/CLR types equivalence, nested objects (creates if they don't exist), lists and arrays.
var populator = new JsonPopulator();
var obj = new MyClass();
populator.PopulateObject(obj, "{\"Title\":\"Startpage\",\"Link\":\"/index\"}");
populator.PopulateObject(obj, "{\"Head\":\"Latest news\",\"Link\":\"/news\"}");
public class MyClass
{
public string Title { get; set; }
public string Head { get; set; }
public string Link { get; set; }
}
Note it doesn't support all of what you would probably expect, but you can override or customize it. Things that could be added: 1) naming convention. You'd have to override the GetProperty method. 2) dictionaries or expando objects. 3) performance can be improved because it uses Reflection instead of MemberAccessor/delegate techniques
public class JsonPopulator
{
public void PopulateObject(object obj, string jsonString, JsonSerializerOptions options = null) => PopulateObject(obj, jsonString != null ? Encoding.UTF8.GetBytes(jsonString) : null, options);
public virtual void PopulateObject(object obj, ReadOnlySpan<byte> jsonData, JsonSerializerOptions options = null)
{
options ??= new JsonSerializerOptions();
var state = new JsonReaderState(new JsonReaderOptions { AllowTrailingCommas = options.AllowTrailingCommas, CommentHandling = options.ReadCommentHandling, MaxDepth = options.MaxDepth });
var reader = new Utf8JsonReader(jsonData, isFinalBlock: true, state);
new Worker(this, reader, obj, options);
}
protected virtual PropertyInfo GetProperty(ref Utf8JsonReader reader, JsonSerializerOptions options, object obj, string propertyName)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
if (propertyName == null)
throw new ArgumentNullException(nameof(propertyName));
var prop = obj.GetType().GetProperty(propertyName);
return prop;
}
protected virtual bool SetPropertyValue(ref Utf8JsonReader reader, JsonSerializerOptions options, object obj, string propertyName)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
if (propertyName == null)
throw new ArgumentNullException(nameof(propertyName));
var prop = GetProperty(ref reader, options, obj, propertyName);
if (prop == null)
return false;
if (!TryReadPropertyValue(ref reader, options, prop.PropertyType, out var value))
return false;
prop.SetValue(obj, value);
return true;
}
protected virtual bool TryReadPropertyValue(ref Utf8JsonReader reader, JsonSerializerOptions options, Type propertyType, out object value)
{
if (propertyType == null)
throw new ArgumentNullException(nameof(reader));
if (reader.TokenType == JsonTokenType.Null)
{
value = null;
return !propertyType.IsValueType || Nullable.GetUnderlyingType(propertyType) != null;
}
if (propertyType == typeof(object)) { value = ReadValue(ref reader); return true; }
if (propertyType == typeof(string)) { value = JsonSerializer.Deserialize<JsonElement>(ref reader, options).GetString(); return true; }
if (propertyType == typeof(int) && reader.TryGetInt32(out var i32)) { value = i32; return true; }
if (propertyType == typeof(long) && reader.TryGetInt64(out var i64)) { value = i64; return true; }
if (propertyType == typeof(DateTime) && reader.TryGetDateTime(out var dt)) { value = dt; return true; }
if (propertyType == typeof(DateTimeOffset) && reader.TryGetDateTimeOffset(out var dto)) { value = dto; return true; }
if (propertyType == typeof(Guid) && reader.TryGetGuid(out var guid)) { value = guid; return true; }
if (propertyType == typeof(decimal) && reader.TryGetDecimal(out var dec)) { value = dec; return true; }
if (propertyType == typeof(double) && reader.TryGetDouble(out var dbl)) { value = dbl; return true; }
if (propertyType == typeof(float) && reader.TryGetSingle(out var sgl)) { value = sgl; return true; }
if (propertyType == typeof(uint) && reader.TryGetUInt32(out var ui32)) { value = ui32; return true; }
if (propertyType == typeof(ulong) && reader.TryGetUInt64(out var ui64)) { value = ui64; return true; }
if (propertyType == typeof(byte[]) && reader.TryGetBytesFromBase64(out var bytes)) { value = bytes; return true; }
if (propertyType == typeof(bool))
{
if (reader.TokenType == JsonTokenType.False || reader.TokenType == JsonTokenType.True)
{
value = reader.GetBoolean();
return true;
}
}
// fallback here
return TryConvertValue(ref reader, propertyType, out value);
}
protected virtual object ReadValue(ref Utf8JsonReader reader)
{
switch (reader.TokenType)
{
case JsonTokenType.False: return false;
case JsonTokenType.True: return true;
case JsonTokenType.Null: return null;
case JsonTokenType.String: return reader.GetString();
case JsonTokenType.Number: // is there a better way?
if (reader.TryGetInt32(out var i32))
return i32;
if (reader.TryGetInt64(out var i64))
return i64;
if (reader.TryGetUInt64(out var ui64)) // uint is already handled by i64
return ui64;
if (reader.TryGetSingle(out var sgl))
return sgl;
if (reader.TryGetDouble(out var dbl))
return dbl;
if (reader.TryGetDecimal(out var dec))
return dec;
break;
}
throw new NotSupportedException();
}
// we're here when json types & property types don't match exactly
protected virtual bool TryConvertValue(ref Utf8JsonReader reader, Type propertyType, out object value)
{
if (propertyType == null)
throw new ArgumentNullException(nameof(reader));
if (propertyType == typeof(bool))
{
if (reader.TryGetInt64(out var i64)) // one size fits all
{
value = i64 != 0;
return true;
}
}
// TODO: add other conversions
value = null;
return false;
}
protected virtual object CreateInstance(ref Utf8JsonReader reader, Type propertyType)
{
if (propertyType.GetConstructor(Type.EmptyTypes) == null)
return null;
// TODO: handle custom instance creation
try
{
return Activator.CreateInstance(propertyType);
}
catch
{
// swallow
return null;
}
}
private class Worker
{
private readonly Stack<WorkerProperty> _properties = new Stack<WorkerProperty>();
private readonly Stack<object> _objects = new Stack<object>();
public Worker(JsonPopulator populator, Utf8JsonReader reader, object obj, JsonSerializerOptions options)
{
_objects.Push(obj);
WorkerProperty prop;
WorkerProperty peek;
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonTokenType.PropertyName:
prop = new WorkerProperty();
prop.PropertyName = Encoding.UTF8.GetString(reader.ValueSpan);
_properties.Push(prop);
break;
case JsonTokenType.StartObject:
case JsonTokenType.StartArray:
if (_properties.Count > 0)
{
object child = null;
var parent = _objects.Peek();
PropertyInfo pi = null;
if (parent != null)
{
pi = populator.GetProperty(ref reader, options, parent, _properties.Peek().PropertyName);
if (pi != null)
{
child = pi.GetValue(parent); // mimic ObjectCreationHandling.Auto
if (child == null && pi.CanWrite)
{
if (reader.TokenType == JsonTokenType.StartArray)
{
if (!typeof(IList).IsAssignableFrom(pi.PropertyType))
break; // don't create if we can't handle it
}
if (reader.TokenType == JsonTokenType.StartArray && pi.PropertyType.IsArray)
{
child = Activator.CreateInstance(typeof(List<>).MakeGenericType(pi.PropertyType.GetElementType())); // we can't add to arrays...
}
else
{
child = populator.CreateInstance(ref reader, pi.PropertyType);
if (child != null)
{
pi.SetValue(parent, child);
}
}
}
}
}
if (reader.TokenType == JsonTokenType.StartObject)
{
_objects.Push(child);
}
else if (child != null) // StartArray
{
peek = _properties.Peek();
peek.IsArray = pi.PropertyType.IsArray;
peek.List = (IList)child;
peek.ListPropertyType = GetListElementType(child.GetType());
peek.ArrayPropertyInfo = pi;
}
}
break;
case JsonTokenType.EndObject:
_objects.Pop();
if (_properties.Count > 0)
{
_properties.Pop();
}
break;
case JsonTokenType.EndArray:
if (_properties.Count > 0)
{
prop = _properties.Pop();
if (prop.IsArray)
{
var array = Array.CreateInstance(GetListElementType(prop.ArrayPropertyInfo.PropertyType), prop.List.Count); // array is finished, convert list into a real array
prop.List.CopyTo(array, 0);
prop.ArrayPropertyInfo.SetValue(_objects.Peek(), array);
}
}
break;
case JsonTokenType.False:
case JsonTokenType.Null:
case JsonTokenType.Number:
case JsonTokenType.String:
case JsonTokenType.True:
peek = _properties.Peek();
if (peek.List != null)
{
if (populator.TryReadPropertyValue(ref reader, options, peek.ListPropertyType, out var item))
{
peek.List.Add(item);
}
break;
}
prop = _properties.Pop();
var current = _objects.Peek();
if (current != null)
{
populator.SetPropertyValue(ref reader, options, current, prop.PropertyName);
}
break;
}
}
}
private static Type GetListElementType(Type type)
{
if (type.IsArray)
return type.GetElementType();
foreach (Type iface in type.GetInterfaces())
{
if (!iface.IsGenericType) continue;
if (iface.GetGenericTypeDefinition() == typeof(IDictionary<,>)) return iface.GetGenericArguments()[1];
if (iface.GetGenericTypeDefinition() == typeof(IList<>)) return iface.GetGenericArguments()[0];
if (iface.GetGenericTypeDefinition() == typeof(ICollection<>)) return iface.GetGenericArguments()[0];
if (iface.GetGenericTypeDefinition() == typeof(IEnumerable<>)) return iface.GetGenericArguments()[0];
}
return typeof(object);
}
}
private class WorkerProperty
{
public string PropertyName;
public IList List;
public Type ListPropertyType;
public bool IsArray;
public PropertyInfo ArrayPropertyInfo;
public override string ToString() => PropertyName;
}
}
The workaround can also be as simple as this (supports multi-level JSON as well):
using System;
using System.Linq;
using System.Reflection;
using System.Text.Json.Serialization;
namespace ConsoleApp
{
public class Model
{
public Model()
{
SubModel = new SubModel();
}
public string Title { get; set; }
public string Head { get; set; }
public string Link { get; set; }
public SubModel SubModel { get; set; }
}
public class SubModel
{
public string Name { get; set; }
public string Description { get; set; }
}
class Program
{
static void Main(string[] args)
{
var model = new Model();
Console.WriteLine(JsonSerializer.ToString(model));
var json1 = "{ \"Title\": \"Startpage\", \"Link\": \"/index\" }";
model = Map<Model>(model, json1);
Console.WriteLine(JsonSerializer.ToString(model));
var json2 = "{ \"Head\": \"Latest news\", \"Link\": \"/news\", \"SubModel\": { \"Name\": \"Reyan Chougle\" } }";
model = Map<Model>(model, json2);
Console.WriteLine(JsonSerializer.ToString(model));
var json3 = "{ \"Head\": \"Latest news\", \"Link\": \"/news\", \"SubModel\": { \"Description\": \"I am a Software Engineer\" } }";
model = Map<Model>(model, json3);
Console.WriteLine(JsonSerializer.ToString(model));
var json4 = "{ \"Head\": \"Latest news\", \"Link\": \"/news\", \"SubModel\": { \"Description\": \"I am a Software Programmer\" } }";
model = Map<Model>(model, json4);
Console.WriteLine(JsonSerializer.ToString(model));
Console.ReadKey();
}
public static T Map<T>(T obj, string jsonString) where T : class
{
var newObj = JsonSerializer.Parse<T>(jsonString);
foreach (var property in newObj.GetType().GetProperties())
{
if (obj.GetType().GetProperties().Any(x => x.Name == property.Name && property.GetValue(newObj) != null))
{
if (property.GetType().IsClass && property.PropertyType.Assembly.FullName == typeof(T).Assembly.FullName)
{
MethodInfo mapMethod = typeof(Program).GetMethod("Map");
MethodInfo genericMethod = mapMethod.MakeGenericMethod(property.GetValue(newObj).GetType());
var obj2 = genericMethod.Invoke(null, new object[] { property.GetValue(newObj), JsonSerializer.ToString(property.GetValue(newObj)) });
foreach (var property2 in obj2.GetType().GetProperties())
{
if (property2.GetValue(obj2) != null)
{
property.GetValue(obj).GetType().GetProperty(property2.Name).SetValue(property.GetValue(obj), property2.GetValue(obj2));
}
}
}
else
{
property.SetValue(obj, property.GetValue(newObj));
}
}
}
return obj;
}
}
}
Output:
I do not know much about this new version of the plug-in, however I found a tutorial that can be followed tutorial with some examples
Based on him I thought of this method and I imagine that he is able to solve his problem
//To populate an existing variable we will do so, we will create a variable with the pre existing data
object PrevData = YourVariableData;
//After this we will map the json received
var NewObj = JsonSerializer.Parse<T>(jsonstring);
CopyValues(NewObj, PrevData)
//I found a function that does what you need, you can use it
//source: https://stackoverflow.com/questions/8702603/merging-two-objects-in-c-sharp
public void CopyValues<T>(T target, T source)
{
if (target == null) throw new ArgumentNullException(nameof(target));
if (source== null) throw new ArgumentNullException(nameof(source));
Type t = typeof(T);
var properties = t.GetProperties(
BindingFlags.Instance | BindingFlags.Public).Where(prop =>
prop.CanRead
&& prop.CanWrite
&& prop.GetIndexParameters().Length == 0);
foreach (var prop in properties)
{
var value = prop.GetValue(source, null);
prop.SetValue(target, value, null);
}
}
This code is based on the answer given by V0ldek.
It adds the use of custom converters if they are defined on properties.
Only properties with public Setter are updated.
/// <summary>
/// Utility class for System.Text.Json
/// </summary>
public static class JsonUtility
{
/// <summary>
/// Update an objet from JSON data
/// </summary>
/// <param name="type">Type of the object to update</param>
/// <param name="target">Object to update</param>
/// <param name="jsonSource">JSON Data</param>
/// <remarks>This code is based on the answer given by V0ldek on StackOverflow</remarks>
/// <see cref="https://stackoverflow.com/a/56906228/3216022"/>
public static void PopulateObject(Type type, object target, string jsonSource, JsonSerializerOptions options)
{
var json = JsonDocument.Parse(jsonSource).RootElement;
foreach (var property in json.EnumerateObject())
OverwriteProperty(property);
void OverwriteProperty(JsonProperty updatedProperty)
{
var propertyInfo = type.GetProperty(updatedProperty.Name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
if (!(propertyInfo?.SetMethod?.IsPublic).GetValueOrDefault())
return;
if (propertyInfo.GetCustomAttribute<JsonIgnoreAttribute>() != null)
return;
// If the property has a Converter attribute, we use it
var converter = GetJsonConverter(propertyInfo);
if (converter != null)
{
var serializerOptions = new JsonSerializerOptions(options);
serializerOptions.Converters.Add(converter);
var parsedValue = JsonSerializer.Deserialize(updatedProperty.Value.GetRawText(), propertyInfo.PropertyType, serializerOptions);
propertyInfo.SetValue(target, parsedValue);
}
else
{
var parsedValue = JsonSerializer.Deserialize(updatedProperty.Value.GetRawText(), propertyInfo.PropertyType, options);
propertyInfo.SetValue(target, parsedValue);
}
}
}
/// <summary>
/// Return the JSON Converter of a property (null if not exists)
/// </summary>
/// <param name="propertyInfo">Property</param>
/// <see cref="https://github.com/dotnet/runtime/blob/v6.0.3/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs"/>
public static JsonConverter GetJsonConverter(PropertyInfo propertyInfo)
{
var attribute = propertyInfo.GetCustomAttribute<JsonConverterAttribute>();
if (attribute != null)
{
if (attribute.ConverterType == null)
return attribute.CreateConverter(propertyInfo.PropertyType);
else
{
var ctor = attribute.ConverterType.GetConstructor(Type.EmptyTypes);
if (typeof(JsonConverter).IsAssignableFrom(attribute.ConverterType) && (ctor?.IsPublic).GetValueOrDefault())
return (JsonConverter)Activator.CreateInstance(attribute.ConverterType)!;
}
}
return null;
}
}
If you already use AutoMapper in your project or don't mind having dependency on it, you can merge objects in a following way:
var configuration = new MapperConfiguration(cfg => cfg
.CreateMap<Model, Model>()
.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != default)));
var mapper = configuration.CreateMapper();
var destination = new Model {Title = "Startpage", Link = "/index"};
var source = new Model {Head = "Latest news", Link = "/news"};
mapper.Map(source, destination);
class Model
{
public string Head { get; set; }
public string Title { get; set; }
public string Link { get; set; }
}
I am not sure if this will fix your problem, but it should work as a temporary workaround. All I did was write a simple class with a populateobject method in it.
public class MyDeserializer
{
public static string PopulateObject(string[] jsonStrings)
{
Dictionary<string, object> fullEntity = new Dictionary<string, object>();
if (jsonStrings != null && jsonStrings.Length > 0)
{
for (int i = 0; i < jsonStrings.Length; i++)
{
var myEntity = JsonSerializer.Parse<Dictionary<string, object>>(jsonStrings[i]);
foreach (var key in myEntity.Keys)
{
if (!fullEntity.ContainsKey(key))
{
fullEntity.Add(key, myEntity[key]);
}
else
{
fullEntity[key] = myEntity[key];
}
}
}
}
return JsonSerializer.ToString(fullEntity);
}
}
I put it into a console app for testing purposes. Below is the entire app if you would like to test it yourself.
using System;
using System.Text.Json;
using System.IO;
using System.Text.Json.Serialization;
namespace JsonQuestion1
{
class Program
{
static void Main(string[] args)
{
// Only used for testing
string path = #"C:\Users\Path\To\JsonFiles";
string st1 = File.ReadAllText(path + #"\st1.json");
string st2 = File.ReadAllText(path + #"\st2.json");
// Only used for testing ^^^
string myObject = MyDeserializer.PopulateObject(new[] { st1, st2 } );
Console.WriteLine(myObject);
Console.ReadLine();
}
}
public class MyDeserializer
{
public static string PopulateObject(string[] jsonStrings)
{
Dictionary<string, object> fullEntity = new Dictionary<string, object>();
if (jsonStrings != null && jsonStrings.Length > 0)
{
for (int i = 0; i < jsonStrings.Length; i++)
{
var myEntity = JsonSerializer.Parse<Dictionary<string, object>>(jsonStrings[i]);
foreach (var key in myEntity.Keys)
{
if (!fullEntity.ContainsKey(key))
{
fullEntity.Add(key, myEntity[key]);
}
else
{
fullEntity[key] = myEntity[key];
}
}
}
}
return JsonSerializer.ToString(fullEntity);
}
}
}
Json File Contents:
st1.json
{
"Title": "Startpage",
"Link": "/index"
}
st2.json
{
"Title": "Startpage",
"Head": "Latest news",
"Link": "/news"
}
If its just one usage and you don't want to add extra dependencies / lots of code, you don't mind a bit of inefficiency and I've not missed something obvious, you can just use:
private static T ParseWithTemplate<T>(T template, string input)
{
var ignoreNulls = new JsonSerializerOptions() { IgnoreNullValues = true };
var templateJson = JsonSerializer.ToString(template, ignoreNulls);
var combinedData = templateJson.TrimEnd('}') + "," + input.TrimStart().TrimStart('{');
return JsonSerializer.Parse<T>(combinedData);
}
Related
Update: We are familiar with Automapper but we have code that maps our data tables to models and will be easy to add the SqlTable attribute. And thus we want to be able to do this with simple extensions versus having to do it through Automapper.
Our legacy database has some bad naming practices. In creating our DateModels, we’ve got rid of a lot of the prefixes and extensions.
What we’re working on is a way to take the EF entities we get back from the database and using reflection copy the property values over into our DataModels.
For a simple class, we have everything work. The issue we haven’t figured out just yet is how to handle collections.
A sample of our sql tables would be.
Customer Table
Cust_Id
Cust_Name
Cust_ProductId - FK to Product.Id
Product Table
Product_Id
Product_Name
Then our data models would be
public class CustomerModel : BaseCustomerModel
{
[SLSqlTable("Cust_Id")]
public int Id { get; set; }
[SLSqlTable("Cust_Name")]
public string Name { get; set; }
[SLSqlTable("Cust_ProductId")]
public string ProductId { get; set; }
[SLSqlTable("Products")]
public IList<BaseProduct> Products { get; set; }
}
public class BaseProductModel
{
[SLSqlTable("Product_Id")]
public int? Id { get; set; }
[SLSqlTable("Product_Name")]
public string Name { get; set; }
}
We’re using the SLSqlTableAttribute we created to map the names.
Then from the internet, we’re using the following code to copy the data between properties. It works fine for everything except our collections right now and that’s what we’re trying to figure out. We were think, we detect a collection and then some how just recursively call back into CopyToModelProperties.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <returns></returns>
public static T CopyToModelProperties<T>(this object source) where T : class, new()
{
T destination = new T();
Type typeDest = destination.GetType();
Type typeSrc = source.GetType();
// Collect all the valid properties to map
var results = from srcProp in typeSrc.GetProperties()
let targetProperty = typeDest.GetProperty(GetModelPropertyNameFromSqlAttribute(typeDest, srcProp.Name))
where srcProp.CanRead
&& targetProperty != null
&& (targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate)
&& (targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) == 0
select new { sourceProperty = srcProp, targetProperty = targetProperty };
//map the properties
foreach (var props in results)
{
if (props.targetProperty.PropertyType.IsGenericType)
{
var _targetType = props.targetProperty.PropertyType.GetGenericArguments()[0];
// props.sourceProperty.PropertyType.MakeGenericType(_targetType).CopyToModelProperties((dynamic)List<>);
}
else
{
props.targetProperty.SetValue(destination, props.sourceProperty.GetValue(source, null), null);
}
}
return destination;
}
public static string GetModelPropertyNameFromSqlAttribute(Type model, string sqlName)
{
string _ret = "";
var _property = model.GetProperties().Where(w => w.GetCustomAttributes<SLSqlTableAttribute>().Where(w1 => w1.FieldName == sqlName).Any()).FirstOrDefault();
if(_property != null)
{
_ret = _property.Name;
}
return _ret;
}
Here is a sample of code getting customers with all their products.
using (var _dc = new BulkTestEntities())
{
var _custs = _dc.Customers.Include("Products").ToList();
foreach(var cust in _custs)
{
_resp.Add(cust.CopyToModelProperties<CustomerModel>());
}
}
This works fine and the if condition checking IsGenericType works, we just need to figure out what code goes here that handle the collection of products when getting customer back.
We thought it would be a recursive call back to CopyToModelProperties because Products could have a collection inside it as well and that could have a collection so we don’t want to hard code levels.
So the question is how to take props.sourceProperty from the if condition above and copy a collection of SQL Entities over to a collection of DataModels?
I haven't used reflection in a long time but kind of shock something like this wasn't out there or no response.
Digging on the internet, found some code snippets that I pieced together to come up with this. It needs some tweaking but it handles the task we set out to do. Also, not going to handle all scenarios most likely but good enough for us to start and build on. Also not all the PITA configuration with something like AutoMapper. We wrote a program that goes out and gets our sql tables, generates classes and properties and we can easily add our attribute to each property and now all we do is call the functions below to map data.
Code we have to that would use what we came up with.
using (var _dc = new BulkTestEntities())
{
var _custs = _dc.Customers.Include("Products").Include("CustomerType").ToList();
// CopyPropertyData.CopyObjectPropertyData(_custs, _resp);
//foreach(var cust in _custs)
//{
// _resp.Add(cust.CopyToModelProperties<CustomerModel>());
//}
foreach (var cust in _custs)
{
CustomerModel _newCust = new CustomerModel();
SQLExtensions.CopyToModelProperties(cust, _newCust);
// CopyData.CopyObjectData(cust, _newCust);
_resp.Add(_newCust);
}
}
Here are the static classes that will handle an IEnumerable and class.
public static void CopyToModelProperties(object source, object target)
{
Type _typeTarget = target.GetType();
Type _typeSrc = source.GetType();
if (_typeSrc.GetInterfaces().Any(a => a.Name == "IEnumerable") && _typeSrc.IsGenericType && _typeTarget.IsGenericType)
{
// Dynamic allows us to loop through a collection if it's type IEnumerable
dynamic _sourceList = source;
foreach (var _source in _sourceList)
{
// Create a temp class of the generic type of the target list
object _tempTarget = Activator.CreateInstance(_typeTarget.GetGenericArguments()[0]);
//Recursively call to map all child properties
CopyToModelProperties(_source, _tempTarget);
// Add to target collection passed in
target.GetType().GetMethod("Add").Invoke(target, new[] { _tempTarget });
}
}
else
{
var results = from srcProp in _typeSrc.GetProperties()
let targetProperty = _typeTarget.GetProperty(GetModelPropertyNameFromSqlAttribute(_typeTarget, srcProp.Name))
where srcProp.CanRead
&& targetProperty != null
&& (targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate)
&& (targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) == 0
// && targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType)
select new { sourceProperty = srcProp, targetProperty = targetProperty };
foreach (var prop in results)
{
if (prop.targetProperty.CanWrite && prop.sourceProperty.CanRead)
{
object targetValue = prop.targetProperty.GetValue(target, null);
object sourceValue = prop.sourceProperty.GetValue(source, null);
if (sourceValue == null) { continue; }
//if (prop.sourceProperty.PropertyType.IsArray
// && prop.targetProperty.PropertyType.IsArray
// && sourceValue != null)
if(prop.sourceProperty.PropertyType.IsClass && prop.targetProperty.PropertyType != typeof(string))
{
var destinationClass = Activator.CreateInstance(prop.targetProperty.PropertyType);
object copyValue = prop.sourceProperty.GetValue(source);
CopyToModelProperties(copyValue, destinationClass);
prop.targetProperty.SetValue(target, destinationClass);
}// See if there is a better way to do this.
else if (prop.targetProperty.PropertyType.GetInterfaces().Any(a => a.Name == "IEnumerable") && prop.sourceProperty.PropertyType.IsGenericType
&& prop.targetProperty.PropertyType.IsGenericType
&& sourceValue != null)
{
CopyToModelList(source, target, prop.targetProperty, prop.sourceProperty, sourceValue);
}
else
{
// CopySingleData(source, target, prop.targetProperty, prop.sourceProperty, targetValue, sourceValue);
prop.targetProperty.SetValue(target, prop.sourceProperty.GetValue(source, null), null);
}
}
}
}
// return target;
}
private static void CopyToModelList(object source, object target, PropertyInfo piTarget, PropertyInfo piSource, object sourceValue)
{
// int _sourceLength = (int)source.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, source, null);
// First create a generic collection that matches the piTarget being passed in.
var _listType = typeof(List<>);
props.sourceProperty.PropertyType.GetGenericArguments();
var _genericTargetArgs = piTarget.PropertyType.GetGenericArguments();
var _concreteTargetType = _listType.MakeGenericType(_genericTargetArgs);
var _newtargetList = Activator.CreateInstance(_concreteTargetType);
dynamic _sourceList = piSource.GetValue(source, null);
foreach (var _source in _sourceList)
{
object _tempTarget = Activator.CreateInstance(piTarget.PropertyType.GetGenericArguments()[0]);
CopyToModelProperties(_source, _tempTarget);
// here we have to make recursive call to copy data and populate the target list.
//_targetList.Add(CopyObjectPropertyData(_source, (dynamic)new object()));
_newtargetList.GetType().GetMethod("Add").Invoke(_newtargetList, new[] { _tempTarget });
}
piTarget.SetValue(target, _newtargetList, null);
}
public static string GetModelPropertyNameFromSqlAttribute(Type model, string sqlName)
{
string _ret = "";
var _property = model.GetProperties().Where(w => w.GetCustomAttributes<SLSqlTableAttribute>().Where(w1 => w1.FieldName == sqlName).Any()).FirstOrDefault();
if(_property != null)
{
_ret = _property.Name;
}
return _ret;
}
Code is such a PITA to edit on this site, creating a new answer that includes caching. The original posted code performs horribly with 60 records to map. About 5 seconds. The new code with caching, 600 records .36 seconds. In this code we have audit fields and only our mapper can set or change Create audit fields. It may not apply to you and if so, remove. All the code below requires is a simple Sql Attribute such as below. Update that on your model properties if your sql table field names are different than your model property names. So much easier than Automapper because you don't other type of configuration and if your sql properties match your model properties, then you can use the below to create your own mapper method.
[AttributeUsage(AttributeTargets.Property)]
public class SLSqlTableAttribute : Attribute
{
protected string _fieldName { get; set; }
public string FieldName
{
get
{
return _fieldName;
}
set
{
_fieldName = value;
}
}
public SLSqlTableAttribute(string fieldName)
{
FieldName = fieldName;
}
}
public class SourceTargetProperties
{
public PropertyInfo SourceProperty { get; set; }
public PropertyInfo TargetProperty { get; set; }
}
public static class DataMapper
{
static Dictionary<string, List<SourceTargetProperties>> _dictTypeProperties = new Dictionary<string, List<SourceTargetProperties>>();
public static void CopyToSqlProperties(object source, object target, int? userId, DateTimeOffset? modifiedDateTime, bool isInsert)
{
Type _typeTarget = target.GetType();
Type _typeSrc = source.GetType();
// If we're passed in a collection
if (_typeSrc.GetInterfaces().Any(a => a.Name == "IEnumerable") && _typeSrc.IsGenericType && _typeTarget.IsGenericType)
{
dynamic _sourceList = source;
foreach (var _source in _sourceList)
{
object _tempTarget = Activator.CreateInstance(_typeTarget.GetGenericArguments()[0]);
CopyToModelProperties(_source, _tempTarget);
// here we have to make recursive call to copy data and populate the target list.
//_targetList.Add(CopyObjectPropertyData(_source, (dynamic)new object()));
// _newtargetList.GetType().GetMethod("Add").Invoke(_newtargetList, new[] { _tempTarget });
target.GetType().GetMethod("Add").Invoke(target, new[] { _tempTarget });
}
}
else
{
// Collect all the valid properties to map
if (!_dictTypeProperties.ContainsKey(_typeSrc.BaseType.Name))
{
_dictTypeProperties.Add(_typeSrc.BaseType.Name, (from srcProp in _typeSrc.GetProperties()
let targetProperty = _typeTarget.GetProperty(GetModelPropertyNameFromSqlAttribute(_typeTarget, srcProp.Name))
where srcProp.CanRead
&& targetProperty != null
&& (targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate)
&& (targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) == 0
// && targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType)
select new SourceTargetProperties { SourceProperty = srcProp, TargetProperty = targetProperty }).ToList());
}
foreach (var prop in _dictTypeProperties[_typeSrc.BaseType.Name])
{
if (prop.TargetProperty.CanWrite && prop.SourceProperty.CanRead)
{
object targetValue = prop.TargetProperty.GetValue(target, null);
object sourceValue = prop.SourceProperty.GetValue(source, null);
if (sourceValue == null) { continue; }
//if (prop.sourceProperty.PropertyType.IsArray
// && prop.targetProperty.PropertyType.IsArray
// && sourceValue != null)
if (prop.SourceProperty.PropertyType.IsClass && prop.TargetProperty.PropertyType != typeof(string))
{
var destinationClass = Activator.CreateInstance(prop.TargetProperty.PropertyType);
object copyValue = prop.SourceProperty.GetValue(source);
CopyToModelProperties(copyValue, destinationClass);
prop.TargetProperty.SetValue(target, destinationClass);
}// See if there is a better way to do this.
else if (prop.TargetProperty.PropertyType.GetInterfaces().Any(a => a.Name == "IEnumerable") && prop.SourceProperty.PropertyType.IsGenericType
&& prop.TargetProperty.PropertyType.IsGenericType
&& sourceValue != null)
{
CopyToModelList(source, target, prop.TargetProperty, prop.SourceProperty, sourceValue);
}
else
{
string _targetPropertyName = prop.TargetProperty.Name;
if (modifiedDateTime.HasValue && (_targetPropertyName == "CreatedDateTime" || _targetPropertyName == "LastModifiedDateTime" || _targetPropertyName == "CreatedBy" ||
_targetPropertyName == "LastModifiedBy"))
{
switch (_targetPropertyName)
{
case "CreatedDateTime":
if (isInsert)
{
prop.TargetProperty.SetValue(target, modifiedDateTime, null);
}
break;
case "CreatedBy":
if (isInsert)
{
prop.TargetProperty.SetValue(target, userId, null);
}
break;
case "LastModifiedDateTime":
prop.TargetProperty.SetValue(target, modifiedDateTime, null);
break;
case "LastModifiedBy":
prop.TargetProperty.SetValue(target, userId, null);
break;
}
}
else
{
prop.TargetProperty.SetValue(target, prop.SourceProperty.GetValue(source, null), null);
}
}
}
}
}
}
/// <summary>
/// Copy from SQL EF Entities to our Models.
/// Models will have the sql table name as a SLSqlAttribute for this to work.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <returns></returns>
public static void CopyToModelProperties(object source, object target)
{
Type _typeTarget = target.GetType();
Type _typeSrc = source.GetType();
if (_typeSrc.GetInterfaces().Any(a => a.Name == "IEnumerable") && _typeSrc.IsGenericType && _typeTarget.IsGenericType)
{
// figure out a way to take in collections here instead of having to loop through outside of code.
// int _sourceLength = (int)source.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, source, null);
var _listType = typeof(List<>);
//var _genericArgs = props.sourceProperty.PropertyType.GetGenericArguments();
//var _genericTargetArgs = _typeTarget.GetGenericArguments();
//var _concreteTargetType = _listType.MakeGenericType(_genericTargetArgs);
//var _newtargetList = Activator.CreateInstance(_concreteTargetType);
//_newtargetList = piSource.GetValue(source, null);
//var _genericTargetArgs
dynamic _sourceList = source;
//dynamic _sourceList = _typeSrc.GetValue(source, null);
// dynamic _targetList = piTarget.GetValue(target, null);
foreach (var _source in _sourceList)
{
object _tempTarget = Activator.CreateInstance(_typeTarget.GetGenericArguments()[0]);
CopyToModelProperties(_source, _tempTarget);
// here we have to make recursive call to copy data and populate the target list.
//_targetList.Add(CopyObjectPropertyData(_source, (dynamic)new object()));
// _newtargetList.GetType().GetMethod("Add").Invoke(_newtargetList, new[] { _tempTarget });
target.GetType().GetMethod("Add").Invoke(target, new[] { _tempTarget });
}
// _typeTarget.SetValue(target, _newtargetList, null);
}
else
{
// Collect all the valid properties to map
if (!_dictTypeProperties.ContainsKey(_typeSrc.BaseType.Name))
{
_dictTypeProperties.Add(_typeSrc.BaseType.Name, (from srcProp in _typeSrc.GetProperties()
let targetProperty = _typeTarget.GetProperty(GetModelPropertyNameFromSqlAttribute(_typeTarget, srcProp.Name))
where srcProp.CanRead
&& targetProperty != null
&& (targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate)
&& (targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) == 0
// && targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType)
select new SourceTargetProperties { SourceProperty = srcProp, TargetProperty = targetProperty }).ToList());
}
foreach (var prop in _dictTypeProperties[_typeSrc.BaseType.Name])
{
if (prop.TargetProperty.CanWrite && prop.SourceProperty.CanRead)
{
object targetValue = prop.TargetProperty.GetValue(target, null);
object sourceValue = prop.SourceProperty.GetValue(source, null);
if (sourceValue == null) { continue; }
//if (prop.sourceProperty.PropertyType.IsArray
// && prop.targetProperty.PropertyType.IsArray
// && sourceValue != null)
if (prop.SourceProperty.PropertyType.IsClass && prop.TargetProperty.PropertyType != typeof(string))
{
var destinationClass = Activator.CreateInstance(prop.TargetProperty.PropertyType);
object copyValue = prop.SourceProperty.GetValue(source);
CopyToModelProperties(copyValue, destinationClass);
prop.TargetProperty.SetValue(target, destinationClass);
}// See if there is a better way to do this.
else if (prop.TargetProperty.PropertyType.GetInterfaces().Any(a => a.Name == "IEnumerable") && prop.SourceProperty.PropertyType.IsGenericType
&& prop.TargetProperty.PropertyType.IsGenericType
&& sourceValue != null)
{
CopyToModelList(source, target, prop.TargetProperty, prop.SourceProperty, sourceValue);
}
else
{
string _targetPropertyName = prop.TargetProperty.Name;
prop.TargetProperty.SetValue(target, prop.SourceProperty.GetValue(source, null), null);
}
}
}
}
// return target;
}
public static string GetModelPropertyNameFromSqlAttribute(Type model, string sqlName)
{
string _ret = "";
var _property = model.GetProperties().Where(w => w.GetCustomAttributes<SLSqlTableAttribute>().Where(w1 => w1.FieldName == sqlName).Any()).FirstOrDefault();
if (_property != null)
{
_ret = _property.Name;
}
return _ret;
}
private static void CopyToModelList(object source, object target, PropertyInfo piTarget, PropertyInfo piSource, object sourceValue)
{
// int _sourceLength = (int)source.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, source, null);
// First create a generic collection that matches the piTarget being passed in.
var _listType = typeof(List<>);
//var _genericArgs = props.sourceProperty.PropertyType.GetGenericArguments();
var _genericTargetArgs = piTarget.PropertyType.GetGenericArguments();
var _concreteTargetType = _listType.MakeGenericType(_genericTargetArgs);
var _newtargetList = Activator.CreateInstance(_concreteTargetType);
//_newtargetList = piSource.GetValue(source, null);
//var _genericTargetArgs
dynamic _sourceList = piSource.GetValue(source, null);
// dynamic _targetList = piTarget.GetValue(target, null);
foreach (var _source in _sourceList)
{
object _tempTarget = Activator.CreateInstance(piTarget.PropertyType.GetGenericArguments()[0]);
CopyToModelProperties(_source, _tempTarget);
// here we have to make recursive call to copy data and populate the target list.
//_targetList.Add(CopyObjectPropertyData(_source, (dynamic)new object()));
_newtargetList.GetType().GetMethod("Add").Invoke(_newtargetList, new[] { _tempTarget });
}
piTarget.SetValue(target, _newtargetList, null);
}
}
I'm creating a function to loop over an object and its childs.
But I'm having some issue when i try to map from the original object to the new object, here is the code:
public static bool MatchObjectField<T>(this T obj, string objectRoute, string value)
{
try
{
var objectroutelist = objectRoute.Split('.');
var objroute = objectroutelist.First();
var childproperty = typeof(T).GetProperty(objroute);
if (objectroutelist.Count() == 1)
{
if (childproperty.GetValue(obj).ToString().Trim() == value)
{
return true;
}
return false;
}
else
{
var instance = Activator.CreateInstance(childproperty.PropertyType);
//childproperty.SetValue(obj, instance, null);
//childproperty.SetValue(instance, obj, null);
instance.MapValues(childproperty);
instance.MatchObjectField(string.Join(".", objectroutelist.Skip(1)), value);
}
}
catch (Exception e)
{
return false;
}
return false;
}
And here the class where I do the map and contains the issue.
public static void MapValues<T>(this T destination, PropertyInfo orgproperty)
{
var orgvalues = orgproperty.GetPropertiesWithValues();
var instance = Activator.CreateInstance(typeof(T));
foreach (var property in (typeof(T)).GetProperties())
{
try
{
var value = orgvalues.FirstOrDefault(a => a.Key == property.Name);
property.SetValue(instance, value);
}
catch (Exception)
{
property.SetValue(instance, null);
}
}
destination = (T)(object)instance;
}
The idea of the function is call with objectName.MatchObjectField("parent.child.child.child","MyName")
When I try to compare a field in the parent like objectName.MatchObjectField("Country","Ireland") it works perfectly
But when I try to create the child structure doing a call like objectName.MatchObjectField("Country.Address.City","Dublin") it breaks when I try to map to the new object.
What I noticed is that the property destination into the method MapValues<T> is mapped as Country.Address with all the properties as null which is correct.
But (typeof(T)).GetProperties() doesn't return anything.
Also i noticed that Activator.CreateInstance(typeof(T)) retunrs type {object} instead of return Country.Address
that may be the reason why is not working.
Any idea how to fix this?
EDIT: add get properties with values-> it retuns a Dictionary<string, object>
public static Dictionary<string, object> GetPropertiesWithValues(this Object obj, bool includeValueTypes = true)
{
return InternalGetProperties(obj, false, includeValueTypes);
}
private static Dictionary<string, object> InternalGetProperties(Object obj, bool withDefaultValue, bool includeValueTypes = true)
{
Dictionary<string, object> d = new Dictionary<string, object>();
var res = GetPropertiesR(obj, d, "", withDefaultValue, includeValueTypes);
return res;
}
private static Dictionary<string, object> GetPropertiesR(Object obj, Dictionary<string, object> d, string parent, bool searchingForDefaults, bool includeValueTypes)
{
if (obj == null)
return d;
var pis = #obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
if (pis == null || pis.Length == 0)
throw new InvalidOperationException("This object doens't have inner properties");
Func<string, string> formatProperty = (property) => string.Concat(parent, parent == "" ? "" : ".", property);
foreach (var pi in pis)
{
object data = pi.GetValue(#obj, null);
// check if is value type
if (pi.PropertyType.IsValueType)
{
// data is never null
if (!includeValueTypes)
continue;
Type nullableType = Nullable.GetUnderlyingType(pi.PropertyType);
object defaultValue = nullableType != null ? GetDefault(nullableType) : GetDefault(data.GetType());
if (!searchingForDefaults)
{
// check default values.
if (data != null && data.ToString() != defaultValue.ToString())
d.Add(formatProperty(pi.Name), data);
}
else
{
// check default values
if ((nullableType != null && data == null) || data.ToString() == defaultValue.ToString())
d.Add(formatProperty(pi.Name), data);
}
}
else
{
//
// reference types
if (!searchingForDefaults)
{
if (data == default(object))
continue;
if (IsThisPropertyPartofSystemNamespace(pi))
{
// transform list into a string with values.
IEnumerable enumeration = data as IList;
if (enumeration != null)
{
StringBuilder sb = new StringBuilder();
foreach (var i in enumeration)
sb.Append(string.Concat(i.ToString(), ", "));
if (sb.Length >= 2)
sb.Remove(sb.Length - 2, 2);
data = sb.ToString();
}
d.Add(formatProperty(pi.Name), data);
}
else
{
//
// user complex type defined
string ctxParent = string.Concat(parent, parent == "" ? "" : ".", pi.Name);
GetPropertiesR(data, d, ctxParent, searchingForDefaults, includeValueTypes);
}
}
else
{
if (data != default(object))
continue;
d.Add(formatProperty(pi.Name), data);
}
}
}
return d;
}
private static bool IsThisPropertyPartofSystemNamespace(PropertyInfo pi)
{
var systemNames = new HashSet<string>
{
"mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken={TOKENKEY}",
"System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken={TOKENKEY}"
};
var isSystemType = systemNames.Contains(pi.PropertyType.Assembly.FullName);
return isSystemType;
}
private static object GetDefault(Type type)
{
if (type.IsValueType)
{
return Activator.CreateInstance(type);
}
return null;
}
TL;DR: Take a step back. Describe the responsibility of each of the methods separately, and write tests for them that way. Start with the "lowest level" method and work your way up. That will make it a lot easier to see what's going wrong.
There are multiple problems here. First look at these two lines of code:
var instance = Activator.CreateInstance(childproperty.PropertyType);
instance.MapValues(childproperty);
The return type of Activator.CreateInstance is object, so this is effectively:
var instance = Activator.CreateInstance(childproperty.PropertyType);
instance.MapValues<object>(childproperty);
That's not what you want - you want to use childproperty.PropertyType as the type argument to MapValues. You don't know that at compile-time, so it's not a good fit as a type parameter.
But beyond that, your MapValues method has a bigger problem: it basically ignores its parameter. The only time it uses destination is in the last line, when it assigns a new value to it:
destination = (T)(object)instance;
That parameter is a value parameter, so assigning it at the end of the method is pointless.
You should decide what the purpose of MapValues is:
Is it to create an instance and populate it, then return it?
Is it to accept an existing instance and populate that?
Either is simple enough to implement, but at the moment you're half way between the two. Also note that you're only passing in a single PropertyInfo - consider how you'd expect to assign multiple properties with that.
Finally, there's the matter of where the values come from. You're currently calling GetPropertiesWithValues on the PropertyInfo - that's not going to do what I think you expect it to. You need to provide the source object itself, otherwise there's nowhere to get values from.
I found what i need to do in this post: Getting Nested Object Property Value Using Reflection
getting the nested object is an easy way of what i was pretending to do.
public static object GetPropertyValue(object src, string propName)
{
if (src == null) throw new ArgumentException("Value cannot be null.", "src");
if (propName == null) throw new ArgumentException("Value cannot be null.", "propName");
if(propName.Contains("."))//complex type nested
{
var temp = propName.Split(new char[] { '.' }, 2);
return GetPropertyValue(GetPropertyValue(src, temp[0]), temp[1]);
}
else
{
var prop = src.GetType().GetProperty(propName);
return prop != null ? prop.GetValue(src, null) : null;
}
}
public static bool MatchObjectField<T>(this T obj, string objectRoute, string value)
{
try
{
var propvalue = GetPropertyValue(obj, objectRoute);
return ( propvalue.ToString().Trim() == value.Trim());
}
catch (Exception) {
throw;
}
}
We are using Json.Net in our project to serialize and deserialize json objects.
Our entities have some DateTime properties and I would like to be able to convert them into PersianCalender DateTime and to provide them as string in my json object:
for example we have this entity :
public class PersonCertificate
{
public DateTime CertificateDate{get;set;}
}
I would like to have a json object like this :
{
"PersianCertificateDate":"1395/10/10"
}
So I thought that would be great to have an attribute named "AsPersianDate" for example so that I could do something like this:
public class PersonCertificate
{
[JsonIgnore]
[AsPersianDate]
public DateTime CertificateDate{get;set;}
}
I know that I can have a custom contract resolver to intercept json property creation process but I don't know how should I tell Json.Net to deserialize PersianCertificateDate into CertificateDate ?
OK it was far more easier than I thought.Actually ContractResolver is responsible for getting and setting all property values so here's what I have done:
public class EntityContractResolver:DefaultContractResolver
{
private class PersianDateValueProvider:IValueProvider
{
private readonly PropertyInfo _propertyInfo;
public PersianDateValueProvider(PropertyInfo propertyInfo)
{
_propertyInfo = propertyInfo;
}
public void SetValue(object target, object value)
{
try
{
var date = value as string;
if(value==null && _propertyInfo.PropertyType==typeof(DateTime))
throw new InvalidDataException();
_propertyInfo.SetValue(target,date.ToGregorianDate());
}
catch (InvalidDataException)
{
throw new ValidationException(new[]
{
new ValidationError
{
ErrorMessage = "Date is not valid",
FieldName = _propertyInfo.Name,
TypeName = _propertyInfo.DeclaringType.FullName
}
});
}
}
public object GetValue(object target)
{
if(_propertyInfo.PropertyType.IsNullable() && _propertyInfo.GetValue(target)==null) return null;
try
{
return ((DateTime) _propertyInfo.GetValue(target)).ToPersian();
}
catch
{
return string.Empty;
}
}
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var list= base.CreateProperties(type, memberSerialization).ToList();
list.AddRange(type.GetProperties()
.Where(pInfo => IsAttributeDefined(pInfo,typeof(AsPersianDateAttribute))&& (pInfo.PropertyType == typeof (DateTime) || pInfo.PropertyType == typeof (DateTime?)))
.Select(CreatePersianDateTimeProperty));
return list;
}
private JsonProperty CreatePersianDateTimeProperty(PropertyInfo propertyInfo)
{
return new JsonProperty
{
PropertyName = "Persian"+propertyInfo.Name ,
PropertyType = typeof (string),
ValueProvider = new PersianDateValueProvider(propertyInfo),
Readable = true,
Writable = true
};
}
private bool IsAttributeDefined(PropertyInfo propertyInfo,Type attribute)
{
var metaDataAttribute = propertyInfo.DeclaringType.GetCustomAttribute<MetadataTypeAttribute>(true);
var metaDataProperty = metaDataAttribute?.MetadataClassType?.GetProperty(propertyInfo.Name);
var metaDataHasAttribute = metaDataProperty != null && Attribute.IsDefined(metaDataProperty, attribute);
return metaDataHasAttribute || Attribute.IsDefined(propertyInfo, attribute);
}
}
It is possible to define a custom "list-merge" startegy used for the JsonConvert.PopulateObject method?
Example:
I have two models:
class Parent
{
public Guid Uuid { get; set; }
public string Name { get; set; }
public List<Child> Childs { get; set; }
}
class Child
{
public Guid Uuid { get; set; }
public string Name { get; set; }
public int Score { get; set; }
}
My initial JSON:
{
"Uuid":"cf82b1fd-1ca0-4125-9ea2-43d1d71c9bed",
"Name":"John",
"Childs":[
{
"Uuid":"96b93f95-9ce9-441d-bfb0-f44b65f7fe0d",
"Name":"Philip",
"Score":100
},
{
"Uuid":"fe7837e0-9960-4c45-b5ab-4e4658c08ccd",
"Name":"Peter",
"Score":150
},
{
"Uuid":"1d2cdba4-9efb-44fc-a2f3-6b86a5291954",
"Name":"Steve",
"Score":80
}
]
}
and my update JSON:
{
"Uuid":"cf82b1fd-1ca0-4125-9ea2-43d1d71c9bed",
"Childs":[
{
"Uuid":"fe7837e0-9960-4c45-b5ab-4e4658c08ccd",
"Score":170
}
]
}
All I need is to specify a model property (by attribute) used for matching list items (in my case the Uuid property of Child), so calling the JsonConvert.PopulateObject on the object deserialized from my initial JSON with a update JSON (it contains ONLY changed values + Uuids for every object) results to update only list elements contained in the update JSON macthed by Uuid (in my case update a Peter's score) and elements not contained in the update JSON leave without change.
I'm searching for some universal solution - I need to apply it on large JSONs with a lot of nested lists (but every model has some unique property). So I need to recursively call PopulateObject on matched list item.
You could create your own JsonConverter that implements the required merge logic. This is possible because JsonConverter.ReadJson is passed an existingValue parameter that contains the pre-existing contents of the property being deserialized.
Thus:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class JsonMergeKeyAttribute : System.Attribute
{
}
public class KeyedListMergeConverter : JsonConverter
{
readonly IContractResolver contractResolver;
public KeyedListMergeConverter(IContractResolver contractResolver)
{
if (contractResolver == null)
throw new ArgumentNullException("contractResolver");
this.contractResolver = contractResolver;
}
static bool CanConvert(IContractResolver contractResolver, Type objectType, out Type elementType, out JsonProperty keyProperty)
{
elementType = objectType.GetListType();
if (elementType == null)
{
keyProperty = null;
return false;
}
var contract = contractResolver.ResolveContract(elementType) as JsonObjectContract;
if (contract == null)
{
keyProperty = null;
return false;
}
keyProperty = contract.Properties.Where(p => p.AttributeProvider.GetAttributes(typeof(JsonMergeKeyAttribute), true).Count > 0).SingleOrDefault();
return keyProperty != null;
}
public override bool CanConvert(Type objectType)
{
Type elementType;
JsonProperty keyProperty;
return CanConvert(contractResolver, objectType, out elementType, out keyProperty);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (contractResolver != serializer.ContractResolver)
throw new InvalidOperationException("Inconsistent contract resolvers");
Type elementType;
JsonProperty keyProperty;
if (!CanConvert(contractResolver, objectType, out elementType, out keyProperty))
throw new JsonSerializationException(string.Format("Invalid input type {0}", objectType));
if (reader.TokenType == JsonToken.Null)
return existingValue;
var list = existingValue as IList;
if (list == null || list.Count == 0)
{
list = list ?? (IList)contractResolver.ResolveContract(objectType).DefaultCreator();
serializer.Populate(reader, list);
}
else
{
var jArray = JArray.Load(reader);
var comparer = new KeyedListMergeComparer();
var lookup = jArray.ToLookup(i => i[keyProperty.PropertyName].ToObject(keyProperty.PropertyType, serializer), comparer);
var done = new HashSet<JToken>();
foreach (var item in list)
{
var key = keyProperty.ValueProvider.GetValue(item);
var replacement = lookup[key].Where(v => !done.Contains(v)).FirstOrDefault();
if (replacement != null)
{
using (var subReader = replacement.CreateReader())
serializer.Populate(subReader, item);
done.Add(replacement);
}
}
// Populate the NEW items into the list.
if (done.Count < jArray.Count)
foreach (var item in jArray.Where(i => !done.Contains(i)))
{
list.Add(item.ToObject(elementType, serializer));
}
}
return list;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
class KeyedListMergeComparer : IEqualityComparer<object>
{
#region IEqualityComparer<object> Members
bool IEqualityComparer<object>.Equals(object x, object y)
{
if (object.ReferenceEquals(x, y))
return true;
else if (x == null || y == null)
return false;
return x.Equals(y);
}
int IEqualityComparer<object>.GetHashCode(object obj)
{
if (obj == null)
return 0;
return obj.GetHashCode();
}
#endregion
}
}
public static class TypeExtensions
{
public static Type GetListType(this Type type)
{
while (type != null)
{
if (type.IsGenericType)
{
var genType = type.GetGenericTypeDefinition();
if (genType == typeof(List<>))
return type.GetGenericArguments()[0];
}
type = type.BaseType;
}
return null;
}
}
Notice that the converter needs to know the IContractResolver currently in use. Having it makes finding the key parameter easier, and also ensures that, if the key parameter has a [JsonProperty(name)] attribute, the replacement name is respected.
Then add the attribute:
class Child
{
[JsonMergeKey]
[JsonProperty("Uuid")] // Replacement name for testing
public Guid UUID { get; set; }
public string Name { get; set; }
public int Score { get; set; }
}
And use the converter as follows:
var serializer = JsonSerializer.CreateDefault();
var converter = new KeyedListMergeConverter(serializer.ContractResolver);
serializer.Converters.Add(converter);
using (var reader = new StringReader(updateJson))
{
serializer.Populate(reader, parent);
}
The converter assumes that the key parameter is always present in the JSON. Also, if any entries in the JSON being merged have keys that are not found in the existing list, they are appended to the list.
Update
The original converter is specifically hardcoded for List<T>, and takes advantage of the fact that List<T> implements both IList<T> and IList. If your collection is not a List<T> but still implements IList<T>, the following should work:
public class KeyedIListMergeConverter : JsonConverter
{
readonly IContractResolver contractResolver;
public KeyedIListMergeConverter(IContractResolver contractResolver)
{
if (contractResolver == null)
throw new ArgumentNullException("contractResolver");
this.contractResolver = contractResolver;
}
static bool CanConvert(IContractResolver contractResolver, Type objectType, out Type elementType, out JsonProperty keyProperty)
{
if (objectType.IsArray)
{
// Not implemented for arrays, since they cannot be resized.
elementType = null;
keyProperty = null;
return false;
}
var elementTypes = objectType.GetIListItemTypes().ToList();
if (elementTypes.Count != 1)
{
elementType = null;
keyProperty = null;
return false;
}
elementType = elementTypes[0];
var contract = contractResolver.ResolveContract(elementType) as JsonObjectContract;
if (contract == null)
{
keyProperty = null;
return false;
}
keyProperty = contract.Properties.Where(p => p.AttributeProvider.GetAttributes(typeof(JsonMergeKeyAttribute), true).Count > 0).SingleOrDefault();
return keyProperty != null;
}
public override bool CanConvert(Type objectType)
{
Type elementType;
JsonProperty keyProperty;
return CanConvert(contractResolver, objectType, out elementType, out keyProperty);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (contractResolver != serializer.ContractResolver)
throw new InvalidOperationException("Inconsistent contract resolvers");
Type elementType;
JsonProperty keyProperty;
if (!CanConvert(contractResolver, objectType, out elementType, out keyProperty))
throw new JsonSerializationException(string.Format("Invalid input type {0}", objectType));
if (reader.TokenType == JsonToken.Null)
return existingValue;
var method = GetType().GetMethod("ReadJsonGeneric", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
var genericMethod = method.MakeGenericMethod(new[] { elementType });
try
{
return genericMethod.Invoke(this, new object[] { reader, objectType, existingValue, serializer, keyProperty });
}
catch (TargetInvocationException ex)
{
// Wrap the TargetInvocationException in a JsonSerializationException
throw new JsonSerializationException("ReadJsonGeneric<T> error", ex);
}
}
object ReadJsonGeneric<T>(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer, JsonProperty keyProperty)
{
var list = existingValue as IList<T>;
if (list == null || list.Count == 0)
{
list = list ?? (IList<T>)contractResolver.ResolveContract(objectType).DefaultCreator();
serializer.Populate(reader, list);
}
else
{
var jArray = JArray.Load(reader);
var comparer = new KeyedListMergeComparer();
var lookup = jArray.ToLookup(i => i[keyProperty.PropertyName].ToObject(keyProperty.PropertyType, serializer), comparer);
var done = new HashSet<JToken>();
foreach (var item in list)
{
var key = keyProperty.ValueProvider.GetValue(item);
var replacement = lookup[key].Where(v => !done.Contains(v)).FirstOrDefault();
if (replacement != null)
{
using (var subReader = replacement.CreateReader())
serializer.Populate(subReader, item);
done.Add(replacement);
}
}
// Populate the NEW items into the list.
if (done.Count < jArray.Count)
foreach (var item in jArray.Where(i => !done.Contains(i)))
{
list.Add(item.ToObject<T>(serializer));
}
}
return list;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
class KeyedListMergeComparer : IEqualityComparer<object>
{
#region IEqualityComparer<object> Members
bool IEqualityComparer<object>.Equals(object x, object y)
{
return object.Equals(x, y);
}
int IEqualityComparer<object>.GetHashCode(object obj)
{
if (obj == null)
return 0;
return obj.GetHashCode();
}
#endregion
}
}
public static class TypeExtensions
{
public static IEnumerable<Type> GetInterfacesAndSelf(this Type type)
{
if (type == null)
throw new ArgumentNullException();
if (type.IsInterface)
return new[] { type }.Concat(type.GetInterfaces());
else
return type.GetInterfaces();
}
public static IEnumerable<Type> GetIListItemTypes(this Type type)
{
foreach (Type intType in type.GetInterfacesAndSelf())
{
if (intType.IsGenericType
&& intType.GetGenericTypeDefinition() == typeof(IList<>))
{
yield return intType.GetGenericArguments()[0];
}
}
}
}
Note that merging is not implemented for arrays since they are not resizable.
When I run the following code, it only returns a MethodInfo/FieldInfo/etc. that belongs directly to the Type that I'm searching for the info object in. How do I find the info object regardless of the fact that it resides in a base class and could be private?
obj.GetType().GetMethod(methodName, bindingFlags);
Well, you answered your own question, but as far as I understood, you main requirement is How do I find the info object regardless of where it is found in the hierarchy?
You don't need recursion here to get all members in the full hierarchy. You can use GetMembers function on a Type and it will return all members including all base classes.
Next code example demonstrates this:
var names =
typeof(MyClass).GetMembers()
.Select (x => x.Name);
Console.WriteLine (string.Join(Environment.NewLine, names));
for such structure
class MyClass : Base
{
public string Name { get; set; }
public string Surname { get; set; }
}
class Base
{
public string Name { get; set; }
}
returns
get_Name
set_Name
get_Surname
set_Surname
get_Name
set_Name
ToString
Equals
GetHashCode
GetType
.ctor
Name
Surname
note that get_Name accessor for auto-property appears twice because MyClass hides Name property of the base class. Also note ToString, GetType and other methods, defined in object class
The following code will look through each base class of an object if the info object is not found in the child class. Note that though it will return the base class info object, it will return the object that it "runs into first", so if you have a variable called _blah in your child class and a variable called _blah in a base class, then the _blah from the child class will be returned.
public static MethodInfo GetMethodInfo(this Type objType, string methodName, BindingFlags flags, bool isFirstTypeChecked = true)
{
MethodInfo methodInfo = objType.GetMethod(methodName, flags);
if (methodInfo == null && objType.BaseType != null)
{
methodInfo = objType.BaseType.GetMethodInfo(methodName, flags, false);
}
if (methodInfo == null && isFirstTypeChecked)
{
throw new MissingMethodException(String.Format("Method {0}.{1} could not be found with the following BindingFlags: {2}", objType.ReflectedType.FullName, methodName, flags.ToString()));
}
return methodInfo;
}
public static FieldInfo GetFieldInfo(this Type objType, string fieldName, BindingFlags flags, bool isFirstTypeChecked = true)
{
FieldInfo fieldInfo = objType.GetField(fieldName, flags);
if (fieldInfo == null && objType.BaseType != null)
{
fieldInfo = objType.BaseType.GetFieldInfo(fieldName, flags, false);
}
if (fieldInfo == null && isFirstTypeChecked)
{
throw new MissingFieldException(String.Format("Field {0}.{1} could not be found with the following BindingFlags: {2}", objType.ReflectedType.FullName, fieldName, flags.ToString()));
}
return fieldInfo;
}
I altered bsara's implementation to be able to find private members.
I use it like this:
public static void Save(string filename, object obj)
{
try
{
using Stream s = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None);
var b = new BinaryFormatter();
b.Serialize(s, obj);
}
catch(SerializationException e)
{
var type= e.Message.Split("in Assembly")[0].Replace("Type", string.Empty).Replace("'", string.Empty).Trim();
var assembly=e.Message.Split("in Assembly")[1].Split("'")[1];
var atype= Type.GetType(type);
string path = FindObject(new Stack<object>(new object[] { obj }), atype, "[myself]");
throw new SerializationException($"Could not serialize path {path} in {obj.GetType().Name} due to not being able to process {type} from {assembly}. see inner exception for details", e);
}
}
the methods with a small update:
private static bool TrySerialize(object obj)
{
if(obj == null)
return true;
var stream = new MemoryStream();
var bf = new BinaryFormatter();
try
{
bf.Serialize(stream, obj);
}
catch(SerializationException)
{
return false;
}
return true;
}
private static string FindObject(Stack<object> self, Type typeToFind, string path)
{
var _self = self.Peek();
if(self.Where(x => x.Equals(_self)).Count() > 1) return null;
foreach(var prop in _self.GetType().GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic).Where(x => !x.GetCustomAttributes(true).Any(y => y is XmlIgnoreAttribute)))
{
switch(prop.MemberType)
{
case System.Reflection.MemberTypes.Property:
{
var line = string.Format("{0}::{1}", path, prop.Name);
var _prop = prop as PropertyInfo;
if(_prop.GetIndexParameters().Count() > 0) break;
if(typeToFind.IsAssignableFrom(_prop.PropertyType))
return line;
if(_prop.PropertyType.IsPrimitive || _prop.PropertyType == typeof(DateTime) || _prop.PropertyType == typeof(string))
continue;
var subInst = _prop.GetValue(_self, new object[0]);
if(subInst == null)
continue;
if(!TrySerialize(subInst))
{
System.Diagnostics.Debugger.Log(0, "", string.Format("Cannot serialize {0}\n", line));
}
self.Push(subInst);
var result = FindObject(self, typeToFind, line);
self.Pop();
if(result != null)
return result;
}
break;
case System.Reflection.MemberTypes.Field:
{
var line = string.Format("{0}::*{1}", path, prop.Name);
var _prop = prop as FieldInfo;
if(typeToFind.IsAssignableFrom(_prop.FieldType))
return line;
if(_prop.FieldType.IsPrimitive || _prop.FieldType == typeof(DateTime) || _prop.FieldType == typeof(string))
continue;
var subInst = _prop.GetValue(_self);
if(subInst == null)
continue;
if(!TrySerialize(subInst))
{
System.Diagnostics.Debugger.Log(0, "", string.Format("Cannot serialize field {0}\n", line));
}
self.Push(subInst);
var result = FindObject(self, typeToFind, line);
self.Pop();
if(result != null)
return result;
}
break;
case System.Reflection.MemberTypes.Event:
{
var line = string.Format("{0}::!{1}", path, prop.Name);
var _prop = prop as EventInfo;
if(typeToFind.IsAssignableFrom(_prop.EventHandlerType))
return line;
var field = _self.GetType().GetField(_prop.Name,
BindingFlags.NonPublic |BindingFlags.Instance |BindingFlags.GetField);
if(field != null && !field.GetCustomAttributes(true).Any(x => x is NonSerializedAttribute) && !TrySerialize(field.GetValue(_self)))
{
System.Diagnostics.Debugger.Log(0, "", string.Format("Cannot serialize event {0}\n", line));
}
}
break;
case System.Reflection.MemberTypes.Custom:
{
}
break;
default: break;
}
}
if(_self is IEnumerable)
{
var list = (_self as IEnumerable).Cast<object>();
var index = 0;
foreach(var item in list)
{
index++;
self.Push(item);
var result = FindObject(self, typeToFind, string.Format("{0}[{1}]", path, index));
self.Pop();
if(result != null)
return result;
}
}
return null;
}