I am not sure how to seek help and this is kind of unusual I agree, so please pardon me. I'll try to explain as below:
• I am consuming JSON using POST and getting a dynamic object. I need to convert all incoming properties in dynamic object to uppercase.
• I am using what I think is a bodged solution. I'm converting JSON to string Dictionary, then putting values into a new dictionary after converting the Key to Key.ToUpper() and then deserializing it back into a dynamic object.
Current working solution is as follows:
string _StrJSON = #"{""FIELDA"" : ""1234"",""fieldb"" : ""OtherValue""}";
var d = JsonConvert.DeserializeObject<Dictionary<string, string>>(_StrJSON);
d.ContainsKey("FIELDB").Dump(); // returns false, obviously.
Dictionary<string, string> dr = new Dictionary<string, string>();
d.ToList().ForEach(r => dr.Add(r.Key.ToUpper(), r.Value));
dr.Dump("dr"); // FIELDA 1234 - FIELDB OtherValue
var a = JsonConvert.SerializeObject(dr);
a.Dump(); // {"FIELDA":"1234","FIELDB":"OtherValue"}
This works.
[EDIT]
Some confusion about my "var" above and other stuff. I am very clear on what is dynamic and what isn't.
[/EDIT]
What I have tried so far which DOES NOT WORK is as below:
namespace Newtonsoft.Json
{
/// <summary>
/// Converts JSON keys to uppercase.
/// </summary>
public class UppercaseContractResolver : Serialization.DefaultContractResolver
{
#region Methods.
#region Public Methods.
/// <summary>
/// Resolves property name for this JSON object.
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
protected override string ResolvePropertyName(string key)
{
return key.ToUpper();
}
#endregion
#endregion
#region Constructors.
/// <summary>
/// Converts JSON keys to uppercase.
/// </summary>
public UppercaseContractResolver()
{
}
#endregion
}
/// <summary>
/// Wrapper for Newtonsoft.Json.JsonConvert for JSON keys as uppercase.
/// </summary>
public static class JsonConvertUpper
{
#region Members.
#endregion
#region Methods.
#region Public Methods.
/// <summary>
/// Tries to serialize specified object with JSON keys in upper case.
/// <para>e.g. "key":value should become "KEY":value by using this method.</para>
/// </summary>
/// <param name="value">Object.</param>
/// <returns></returns>
public static string SerializeObjectUpper(object value)
{
return JsonConvert.SerializeObject(value, new JsonSerializerSettings
{
ContractResolver = new UppercaseContractResolver()
});
}
/// <summary>
/// Tries to Deserialize specified object with JSON keys in upper case.
/// <para>e.g. "key":value should become "KEY":value by using this method.</para>
/// </summary>
/// <param name="strValue">String.</param>
/// <returns></returns>
public static object DeserializeObjectUpper(string strValue)
{
// DeserializeObject does not allow uppercase properties. So use SerializeObjectUpper and then deserialize.
var value = JsonConvert.DeserializeObject(strValue);
string strJSON = SerializeObjectUpper(value);
return JsonConvert.DeserializeObject(strJSON, new JsonSerializerSettings()
{
ContractResolver = new UppercaseContractResolver()
});
}
#endregion
#endregion
}
}
The method call for above would be:
dynamic json = JsonConvertUpper.DeserializeObjectUpper(_StrJSON);
if (json.CTN== null)
{...}
[EDIT]
Please note I don't have classes as JSON keys have to be treated as UPPERCASE and classes and other code etc are all in ProperCase.
[/EDIT]
Any way of doing it inside JsonConvert at all? To avoid my manual patch? Any help is appreciated. Thanks.
The reason that your contract resolver does not work is that Json.NET's contract resolver defines how to map JSON from and to the properties of a POCO when serializing -- but when you deserialize to dynamic, Json.NET isn't actually mapping from and to a POCO, it's building up a JToken hierarchy directly from the JSON, the members of which implement IDynamicMetaObjectProvider. I.e.
dynamic o = JsonConvert.DeserializeObject(strJSON);
and
dynamic o = JToken.Parse(strJSON);
are the same.
Given that this is the case, the easiest way to do what you want would be to explicitly convert each property name to uppercase as the hierarchy is being built, like so:
public static class JsonExtensions
{
public static JToken ParseUppercase(string json)
{
using (var textReader = new StringReader(json))
using (var jsonReader = new JsonTextReader(textReader))
{
return jsonReader.ParseUppercase();
}
}
public static JToken ParseUppercase(this JsonReader reader)
{
return reader.Parse(n => n.ToUpperInvariant());
}
public static JToken Parse(this JsonReader reader, Func<string, string> nameMap)
{
JToken token;
using (var writer = new RenamingJTokenWriter(nameMap))
{
writer.WriteToken(reader);
token = writer.Token;
}
return token;
}
}
class RenamingJTokenWriter : JTokenWriter
{
readonly Func<string, string> nameMap;
public RenamingJTokenWriter(Func<string, string> nameMap)
: base()
{
if (nameMap == null)
throw new ArgumentNullException();
this.nameMap = nameMap;
}
public override void WritePropertyName(string name)
{
base.WritePropertyName(nameMap(name));
}
// No need to override WritePropertyName(string name, bool escape) since it calls WritePropertyName(string name)
}
And then use it like:
dynamic json = JsonExtensions.ParseUppercase(_StrJSON);
Sample fiddle.
Related
I want to serialize .NET objects to JSON in a human-readable way, but I would like to have more control about whether an object's properties or array's elements end up on a line of their own.
Currently I'm using JSON.NET's JsonConvert.SerializeObject(object, Formatting, JsonSerializerSettings) method for serialization, but it seems I can only apply the Formatting.Indented (all elements on individual lines) or Formatting.None (everything on a single line without any whitespace) formatting rules globally for the entire object. Is there a way to globally use indenting by default, but turn it off for certain classes or properties, e.g. using attributes or other parameters?
To help you understand the problem, here are some output examples. Using Formatting.None:
{"array":["element 1","element 2","element 3"],"object":{"property1":"value1","property2":"value2"}}
Using Formatting.Indented:
{
"array": [
"element 1",
"element 2",
"element 3"
],
"object": {
"property1": "value1",
"property2":"value2"
}
}
What I would like to see:
{
"array": ["element 1","element 2","element 3"],
"object": {"property1":"value1","property2":"value2"}
}
(I realize my question may be slightly related to this one, but the comments there totally miss the point and don't actually provide a valid answer.)
One possibility would be to write a custom Json converter for the specific types you need special handling and switch the formatting for them:
class Program
{
static void Main()
{
var root = new Root
{
Array = new[] { "element 1", "element 2", "element 3" },
Object = new Obj
{
Property1 = "value1",
Property2 = "value2",
},
};
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
};
settings.Converters.Add(new MyConverter());
string json = JsonConvert.SerializeObject(root, settings);
Console.WriteLine(json);
}
}
public class Root
{
public string[] Array { get; set; }
public Obj Object { get; set; }
}
public class Obj
{
public string Property1 { get; set; }
public string Property2 { get; set; }
}
class MyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string[]) || objectType == typeof(Obj);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue(JsonConvert.SerializeObject(value, Formatting.None));
}
}
This will output:
{
"Array": ["element 1","element 2","element 3"],
"Object": {"Property1":"value1","Property2":"value2"}
}
I also used a converter for this (as per Darin Dimitrov's answer), but instead of calling WriteRawValue() I use the serializer for each element; which ensures any custom converters that apply to the element type will be used.
Note however that this converter is only operating on Arrays of a handful of primitive types, it's not using the Newtonsoft.Json logic for determining what should be serialized as an array and what a primitive type is, basically because that code is internal and I wanted to avoid maintaining a copy of it.
Overall I get the feeling that converters aren't intended to be used for formatting tasks such as this, but I think they're the only option in the current API. Ideally the API would offer a few more formatting options or perhaps better support for custom formatting within the converter API.
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace JsonProto
{
/// <summary>
/// A JsonConverter that modifies formatting of arrays, such that the array elements are serialised to a single line instead of one element per line
/// preceded by indentation whitespace.
/// This converter handles writing JSON only; CanRead returns false.
/// </summary>
/// <remarks>
/// This converter/formatter applies to arrays only and not other collection types. Ideally we would use the existing logic within Newtonsoft.Json for
/// identifying collections of items, as this handles a number of special cases (e.g. string implements IEnumerable over the string characters). In order
/// to avoid duplicating in lots of logic, instead this converter handles only Arrays of a handful of selected primitive types.
/// </remarks>
public class ArrayNoFormattingConverter : JsonConverter
{
# region Static Fields
static HashSet<Type> _primitiveTypeSet =
new HashSet<Type>
{
typeof(char),
typeof(char?),
typeof(bool),
typeof(bool?),
typeof(sbyte),
typeof(sbyte?),
typeof(short),
typeof(short?),
typeof(ushort),
typeof(ushort?),
typeof(int),
typeof(int?),
typeof(byte),
typeof(byte?),
typeof(uint),
typeof(uint?),
typeof(long),
typeof(long?),
typeof(ulong),
typeof(ulong?),
typeof(float),
typeof(float?),
typeof(double),
typeof(double?),
typeof(decimal),
typeof(decimal?),
typeof(string),
typeof(DateTime),
typeof(DateTime?),
};
#endregion
#region Properties
/// <summary>
/// Determines whether this instance can convert the specified object type.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// </returns>
public override bool CanConvert(Type objectType)
{
// Note. Ideally this would match the test for JsonContractType.Array in DefaultContractResolver.CreateContract(),
// but that code is all internal to Newtonsoft.Json.
// Here we elect to take over conversion for Arrays only.
if(!objectType.IsArray) {
return false;
}
// Fast/efficient way of testing for multiple possible primitive types.
Type elemType = objectType.GetElementType();
return _primitiveTypeSet.Contains(elemType);
}
/// <summary>
/// Gets a value indicating whether this <see cref="JsonConverter"/> can read JSON.
/// </summary>
/// <value>Always returns <c>false</c>.</value>
public override bool CanRead
{
get { return false; }
}
#endregion
#region Public Methods
/// <summary>
/// Reads the JSON representation of the object. (Not implemented on this converter).
/// </summary>
/// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
/// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Formatting formatting = writer.Formatting;
writer.WriteStartArray();
try
{
writer.Formatting = Formatting.None;
foreach(object childValue in ((System.Collections.IEnumerable)value)) {
serializer.Serialize(writer, childValue);
}
}
finally
{
writer.WriteEndArray();
writer.Formatting = formatting;
}
}
#endregion
}
}
I have a class DocumentObject that extends DynamicObject to allow dynamic membership attributes.
public class DocumentObject : DynamicObject
{
/// <summary>
/// Inner dictionary that holds the dynamic members of the object
/// </summary>
Dictionary<string, object> dictionary = new Dictionary<string, object>();
/// <summary>
/// Try to get the member that is not defined in the class (additional dynamic members) from inner dictionary
/// </summary>
/// <param name="binder"></param>
/// <param name="result"></param>
/// <returns></returns>
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
// Converting the property name to lowercase
// so that property names become case-insensitive.
string name = binder.Name.ToLower();
// If the property name is found in a dictionary,
// set the result parameter to the property value and return true.
// Otherwise, return false.
return dictionary.TryGetValue(name, out result);
}
/// <summary>
/// Try to set the member that is not defined in the class (additional dynamic members) to inner dictionary
/// </summary>
/// <param name="binder"></param>
/// <param name="value"></param>
/// <returns></returns>
public override bool TrySetMember(SetMemberBinder binder, object value)
{
// Converting the property name to lowercase
// so that property names become case-insensitive.
dictionary[binder.Name.ToLower()] = value;
// You can always add a value to a dictionary,
// so this method always returns true.
return true;
}
/// <summary>
/// Get the names of all the dynamic members
/// </summary>
/// <returns></returns>
public override IEnumerable<string> GetDynamicMemberNames()
{
return dictionary.Keys;
}
}
I have a base Person class that inherits DocumentObject
public class PersonDto : DocumentObject
{
[JsonProperty("id")]
public string Id { get; set; }
}
Another child OfficePersonDto class that inherits PersonDto
public class OfficePersonDto : PersonDto
{
[JsonProperty("name")]
public string Name { get; set; }
}
In my function, I am receiving JSON object that has to be at least PersonDto object, but if its an OfficePersonDto type, I wish to be able to cast PersonDto into OfficePersonDto.
I.e. JSON = {"Id":1, "Name": "Orchard"}, in PersonDto, name attribute will be saved using DocumentObject's dictionary, while casting to OfficePersonDto, both Id and name are attributes of the class.
How can I cast from PersonDto to a child class e.g. OfficePersonDto?
PersonDto personDto = ...
OfficePersonDto off = personDto as OfficePersonDto // results in null or Name is null
Automapper is very helpful when the issue is about these kind of conversions.
I'll show you a quick working example. But it will be still open for more refactoring of course.
PersonDto personDto = ...
// Do not use the following conversion, instead get help from automapper
// OfficePersonDto off = personDto as OfficePersonDto
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<PersonDto, OfficePersonDto>()
.ForMember(d => d.Name, opt => opt.MapFrom(new OfficePersonNameResolver()));
});
var mapper = config.CreateMapper();
var off = mapper.Map<OfficePersonDto>(personDto); // off is created with correct values
OfficePersonNameResolver is like this:
public class OfficePersonNameResolver : IValueResolver<PersonDto, OfficePersonDto, string>
{
public string Resolve(PersonDto source, OfficePersonDto destination, string destMember, ResolutionContext context)
{
return (source as dynamic).Name;
}
}
You are welcome to ask if you have questions about this.
Edit: Generalizing the resolver to IValueResolver<TSource, TDestination, TDestinationMember>
Change the creation of configuration like this:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<PersonDto, OfficePersonDto>()
.ForMember(d => d.Name, opt => opt.MapFrom(new DynamicObjectValueResolver<PersonDto, OfficePersonDto, string>("name")));
});
And The value resolver should be like this now:
public class DynamicObjectValueResolver<TSource, TDestination, TDestinationMember> : IValueResolver<TSource, TDestination, TDestinationMember>
where TDestinationMember : class
{
private readonly string _propertyName;
public DynamicObjectValueResolver(string propertyName)
{
_propertyName = propertyName;
}
public TDestinationMember Resolve(TSource source, TDestination destination, TDestinationMember destMember, ResolutionContext context)
{
dynamic eo = JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(source));
IDictionary<string, object> dictionary = eo;
return dictionary[_propertyName] as TDestinationMember;
}
}
Working example: https://dotnetfiddle.net/KS91To
I'm trying to deserialize an object in an extension class, and it's not working like I would expect. I've read that I can use JsonSerializerSettings to let the JSON deserializer use a private constructor. But for some reason, JsonSerializerSettings aren't working for me. Here's the JSON reader/writer I'm using:
public class FileStorageService<T> : IStorageService<T> where T : IEquatable<T>
/// <summary>
/// Writes the given object instance to a JSON file.
/// Source: https://stackoverflow.com/questions/6201529/turn-c-sharp-object-into-a-json-string-in-net-4
/// </summary>
/// <param name="filePath">The file path to write the object instance to.</param>
/// <param name="objectToWrite">The object instance to write to the file.</param>
///
/// Comment source: https://stackoverflow.com/questions/6115721/how-to-save-restore-serializable-object-to-from-file
public void WriteJSONFile(string filePath, T objectToWrite)
{
string json = JsonConvert.SerializeObject(objectToWrite);
File.WriteAllText(filePath, json);
}
/// <summary>
/// Reads an object instance from a JSON file.
/// </summary>
/// <param name="filePath">The file path to read the object instance from.</param>
/// <returns>Returns a new instance of the object read from the JSON file.</returns>
///
/// Comment source: https://stackoverflow.com/questions/6115721/how-to-save-restore-serializable-object-to-from-file
public T ReadJSONFile(string filePath)
{
if (!File.Exists(filePath))
{
FileStream fs = new FileStream(filePath, FileMode.CreateNew);
fs.Close();
}
JsonSerializerSettings settings = new JsonSerializerSettings
{
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor
};
string json = File.ReadAllText(filePath, Encoding.UTF8);
return JsonConvert.DeserializeObject<T>(json, settings);
}
}
And here's an example of a class I'm trying to read/write:
public class MyObject : IEquatable<MyObject>
{
public string myString { get; set; }
public byte[] myBytes { get; set; }
protected MyHasher hasher;
public MyObject(string first, string second)
{
this.myString = first;
hasher = new MyHasher();
this.myBytes = hasher.ComputeHash(second);
}
... (implementing IEquatable below)
}
When I run the program in Visual Studio, I get a null pointer exception:
"An unhandled exception of type 'System.ArgumentNullException' occurred in Newtonsoft.Json.dll
Additional information: String reference not set to an instance of a String."
...pointing to the hasher.ComputeHash(second) method, with the call stack:
> this.myBytes = hasher.ComputeHash(second);
> return JsonConvert.DeserializeObject<T>(json, settings);
Debugging it, I found that JsonConvert.DeserializeObject is calling MyObject's public constructor and giving it null values (sub-question: how is this possible?), which I don't want. I want it to use the default constructor instead.
I could add the : new()constraint to force objects to have a public parameterless constructor, but I want my extension class to be able to handle any type of object (strings, ints, etc.), not just custom objects I create.
As a final complication, note that I can't change MyObject in any way.
How can I do this?
I have stored immutable types in a temporary CQRS read store (query/read side, in fact implemented by a simple List with abstraction access layer, I don't want to use a full blown document database at this point). These read stores contains items like the following:
public class SomeItem
{
private readonly string name;
private readonly string description;
public SomeItem(string name, string description)
{
this.name = name;
this.description = description;
}
public string Name
{
get { return this.name; }
}
public string Description
{
get { return this.description; }
}
}
Now I want to change the Name and in a 2nd Command the Description.
These changes should keep the current state, which means for the example above:
// initial state
var someItem = new SomeItem("name", "description");
// update name -> newName
someItem = new SomeItem("newName", someItem.Description);
// update description -> newDescription
someItem = new SomeItem(someItem.Name, "newDescription");
This does look error prone to me if you have several properties... you have to manage keeping the current state. I could add something like Clone() to every type but I think/hope there is something better out there that performs well and is easy to use, I don't want to write much repetive code (lazy programmer). Any suggestions how to improve the code above? The SomeItem class needs to stay immutable (transported through several different threads).
Sadly, there's no simple way in C#. F# has the with keyword, and you could have a look at lenses, but it's all somewhat tedious in C#. The best I can give you is something like this:
class SomeItem
{
private readonly string name;
private readonly string description;
public SomeItem(string name, string description)
{
this.name = name;
this.description = description;
}
public SomeItem With
(
Option<string> name = null,
Option<string> description = null
)
{
return new SomeItem
(
name.GetValueOrDefault(this.name),
description.GetValueOrDefault(this.description)
);
}
}
This allows you to do the updates like
var newItem = oldItem.With(name: "My name!");
I've used this approach with extension methods and T4s to great effect, but even when you write the code manually, it's reasonably reliable - if you add a new field, you must add it to the With as well, so it works quite well.
There's a few more approaches if you are willing to tolerate runtime code generation and reducing type safety, but that's kind of going against the grain IMO.
With C#9 we got the with operator for this purpose.
public record Car
{
public string Brand { get; init; }
public string Color { get; init; }
}
var car = new Car{ Brand = "BMW", Color = "Red" };
var anotherCar = car with { Brand = "Tesla"};
With-expressions When working with immutable data, a common pattern is
to create new values from existing ones to represent a new state. For
instance, if our person were to change their last name we would
represent it as a new object that’s a copy of the old one, except with
a different last name. This technique is often referred to as
non-destructive mutation. Instead of representing the person over
time, the record represents the person’s state at a given time. To
help with this style of programming, records allow for a new kind of
expression; the with-expression:
News in C#9
NOTE
With operator is only supported by records.
Records At the core of classic object-oriented programming is the idea that an object has strong identity and encapsulates mutable state
that evolves over time. C# has always worked great for that, But
sometimes you want pretty much the exact opposite, and here C#’s
defaults have tended to get in the way, making things very laborious.
Recods in C#9
What you are looking for is commonly called the with operator:
// returns a new immutable object with just the single property changed
someItem = { someItem with Name = "newName" };
Unfortunately, unlike F#, C# does not have such an operator (yet?).
Other C# developers are missing this feature as well, which is why someone wrote a Fody extension to do exactly that:
https://github.com/mikhailshilkov/With.Fody
Here's another approach, which implements an UpdateWith method manually but requires an Option<T> helper class. Luaan's answer describes this approach in more detail:
Implementing F#-inspired "with" updates for immutable classes in C#
Simple solution
I also thought about this question. Record's are not suitable for my purposes, since it is necessary to interact with EF Core.
I suggest a simple and low-cost way:
add a copy constructor to the class;
Make properties that change during cloning available for initialization;
clone an object with a change through the copy constructor with an initialization list:
var a = new SomeItem("name", "abracadabra");
var b = new SomeItem(a) {Description="descr"};
Simple code
var a = new SomeItem("name", "abracadabra");
var b = new SomeItem(a) {Description="descr"};
public class SomeItem
{
private string name;
private string description;
public SomeItem(string name, string description)
{
Name = name;
Description = description;
}
public SomeItem(SomeItem another): this(another.Name, another.Description)
{
}
public string Name
{
get => name;
init => name = value;
}
public string Description
{
get => description;
init => description = value;
}
}
Extended solution
If the final type is not known at compile time, then this approach is easy to extend. Let's say there is a class "ValueObject" whose derived types we need to clone.
Note: I apologize for the incorrect translation in some places. English version obtained using google.translate
Additional code
using System.Linq.Expressions;
using Light.GuardClauses;
using JetBrains.Annotations;
using static DotNext.Linq.Expressions.ExpressionBuilder;
using ValueObject = Company.Domain....;
/// <summary>
/// The plagiarizer creates a copy of the object with a change in its individual properties using an initializer
/// </summary>
/// <remarks> The foreign object must define a copy constructor, and mutable members must support initialization </remarks>
public struct Plagiarist {
/// <summary>
/// Object to be copied
/// </summary>
private readonly object _alienObject;
/// <summary>
/// Type <see cref="_alienObject" />
/// </summary>
private Type _type => _alienObject.GetType();
/// <summary>
/// Object parsing Expression
/// </summary>
private readonly ParsingInitializationExpression _parser = new();
public Plagiarist(object alienObject) {
_alienObject = alienObject.MustNotBeNullReference();
if (!CopyConstructorIs())
throw new ArgumentException($"Type {_type.FullName} must implement a copy constructor");
}
/// <summary>
/// Does the object we want to plagiarize have a copy constructor?
/// </summary>
/// <returns>True - there is a copy constructor, otherwise - false</returns>
[Pure]
private bool CopyConstructorIs() {
return _type.GetConstructor(new[] { _type }) is not null;
}
/// <summary>
/// Returns a copy of a foreign object with a change in its individual properties using an initializer
/// </summary>
/// <param name="initializer">
/// <see cref="Expression" /> create an object with initialization of those fields,
/// which need to be changed:
/// <code>() => new T() {Member1 = "Changed value1", Member2 = "Changed value2"}</code>
/// or <see cref="Expression" /> create an anonymous type with initialization of those fields
/// that need to be changed:
/// <code>() => new {Member1 = "Changed value1", Member2 = "Changed value2"}</code>
/// </param>
/// <returns></returns>
[Pure]
public object Plagiarize(Expression<Func<object>> initializer) {
var (newValues, constructParam) = _parser.ParseInitialization(initializer);
var constrCopies = _type.New(_alienObject.Const().Convert(_type));
Expression plagiarist = (newValues.Count, constructParam.Count) switch {
(> 0, _) => Expression.MemberInit(constrCopies, newValues.Values),
(0, > 0) => Expression.MemberInit(constrCopies, ConstructorInInitializationList(constructParam).Values),
_ => constrCopies
};
var plagiarize = Expression.Lambda<Func<object>>(plagiarist).Compile();
return plagiarize();
}
[Pure]
public Dictionary<string, MemberAssignment> ConstructorInInitializationList(
Dictionary<string, Expression> constructorParameters) {
Dictionary<string, MemberAssignment> initializer = new();
const BindingFlags flagReflections = BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public;
var allProperties = _type.GetProperties(flagReflections);
var allFields = _type.GetFields(flagReflections);
foreach (var memberName in constructorParameters.Keys) {
var property = allProperties.FirstOrDefault(s => s.Name ==memberName);
var field = allFields.FirstOrDefault(s => s.Name == memberName);
(MemberInfo member, Type memberType) = (property, field) switch {
({ }, _) => (property, property.PropertyType),
(null, { }) => ((MemberInfo)field, field.FieldType),
_ => throw new ArgumentException($"{_type.FullName} does not contain member {memberName}")
};
initializer[memberName] = Expression.Bind(member, constructorParameters[memberName].Convert(memberType));
}
return initializer;
}
/// <summary>
/// Template "Visitor" for traversing the expression tree in order to highlight
/// initialization expression and constructor
/// </summary>
private class ParsingInitializationExpression : ExpressionVisitor {
private Dictionary<string, MemberAssignment>? _initializer;
private Dictionary<string, Expression>? _initializerAnonym;
/// <summary>
/// Parses the expression tree and returns the initializer and constructor parameters
/// </summary>
/// <param name="initializer"><see cref="Expression" /> to parse</param>
/// <returns> tuple of initializer and constructor</returns>
public ParsedInitialization ParseInitialization(Expression initializer) {
_initializer = new Dictionary<string, MemberAssignment>();
_initializerAnonym = new Dictionary<string, Expression>();
Visit(initializer);
return new ParsedInitialization(_initializer, _initializerAnonym);
}
protected override MemberAssignment VisitMemberAssignment(MemberAssignment node) {
_initializer![node.Member.Name] = node;
return base.VisitMemberAssignment(node);
}
protected override Expression VisitNew(NewExpression node) {
foreach (var (member, value) in node.Members?.Zip(node.Arguments) ??
Array.Empty<(MemberInfo First, Expression Second)>())
_initializerAnonym![member.Name] = value;
return base.VisitNew(node);
}
/// <summary>
/// Type to return values from method <see cref="ParseInitialization" />
/// </summary>
/// <param name="Initializer"></param>
/// <param name="ConstructorParameters"></param>
public record struct ParsedInitialization(Dictionary<string, MemberAssignment> Initializer,
Dictionary<string, Expression> ConstructorParameters);
}
}
public static class ValueObjectPlagiarizer{
/// <summary>
/// Creates a copy of the object with a change in its individual properties using an initializer
/// </summary>
/// <param name="alien">Object to be plagiarized</param>
/// <param name="initializer">
/// <see cref="Expression" /> creating an object of type <typeparamref name="T" />
/// with initialization of those fields that need to be changed:
/// <code>ob.Plagiarize(() => new T() {Member1 = "Changed value1", Member2 = "Changed value2"})</code>
/// or <see cref="Expression" /> create an anonymous type with initialization of those fields that need to be changed:
/// <code>ob.Plagiarize(() => new {Member1 = "Changed value1", Member2 = "Changed value2"})</code>
/// </param>
/// <returns>plagiarism of the object</returns>
public static object Plagiarize<T>(this ValueObject alien, Expression<Func<T>> initializer)
where T : class {
var bodyReduced = initializer.Convert<object>();
var initializerReduced = Expression.Lambda<Func<object>>(bodyReduced, initializer.Parameters);
return new Plagiarist(alien).Plagiarize(initializerReduced);
}
}
Usages
If SomeItem is a descendant of ValueObject
ValueObject a = new SomeItem("name", "abracadabra");
// via type constructor
var b = (SomeItem)a.Plagiarize(()=>new SomeItem(null){Description="descr"});
// anonymous type
var c = (SomeItem)a.Plagiarize(()=>new{Description="descr"});
b.Description.Should().Be("descr"); //true
c.Description.Should().Be("descr"); //true
If what you want to do is (as you commented) update the name of an existing object, a readonly property might be bad design.
Otherwise if its really a new object you want to create, you might want your class to implement some interface with a 'Dispose' method.
I am receiving XML from the database and trying to process it into my system. I have generated a class from an XSD that I received from the other team. The problem I'm having is that, right now the property names in the XSD do not match up to the property names within my application (typical). Meaning in order for me to build an object that I can use I have to create an object and piece it together with the XML. Like this:
DynamicEntity auth = new DynamicEntity(EntityAuthorization);
auth.Properties["DealerId] = authprm.new_dealerid[0].Value;
auth.Properties["CarId"] = authprm.new_carid[0].Value;
etc...
I could potentially have 40 to 100 properties to set. I've thought about applying XmlElement to the class attributes but every time I have to regenerate the class (from the XML) I will lose the XmlElements that I applied earlier. What are some suggestions?
Thanks everyone!
You could use a metadata "buddy class", by putting the MetadataTypeAttribute on your class. I would make the class that gets generated partial if it isn't already and then create another partial class and add the MetadataType Attribute to that class. Then if you regenerate the class from the XSD and it's not partial the compiler will complain and you will remember to make it partial.
I don't really like "buddy classes" because the properties get duplicated, not really DRY, but it might be the solution you are looking for. For a simple overview of Metadata Class, which is usually about validation attributes - but it works for any attributes, you can find on MS here
You can see answer https://stackoverflow.com/a/26724847/1798889 for how to make the XmlSerializer know about a buddy class.
Update
Now that I think about this if you don't want to use the Buddy class you can build a fluent api to configure the XML Serializer.
We will keep the CustomAttributeProvider class from the XMLSerializer with buddy class question but instead make an XmlSerializerConfigurator class.
public class XmlSerializerConfigurator<T>
{
private readonly XmlAttributeOverrides _xmlAttributeOverrides;
public XmlSerializerConfigurator(XmlAttributeOverrides xmlAttributeOverrides)
{
_xmlAttributeOverrides = xmlAttributeOverrides;
}
public XmlSerializerConfigurator() : this(new XmlAttributeOverrides())
{
}
/// <summary>
/// Adds attributes to properties or fields and strongly typed
/// </summary>
/// <typeparam name="TData"></typeparam>
/// <param name="property"></param>
/// <param name="xmlAttributes"></param>
/// <returns></returns>
public XmlSerializerConfigurator<T> AddPropertyOrFieldAttributes<TData>(Expression<Func<T, TData>> property,
params Attribute[] xmlAttributes)
{
var memberName = property.GetName();
_xmlAttributeOverrides.Add(typeof (T), memberName,
new XmlAttributes(new CustomAttributeProvider(xmlAttributes)));
return this;
}
/// <summary>
/// Adds class level attributes
/// </summary>
/// <param name="xmlAttributes"></param>
/// <returns></returns>
public XmlSerializerConfigurator<T> AddClassLevelAttributes(params Attribute[] xmlAttributes)
{
_xmlAttributeOverrides.Add(typeof(T), new XmlAttributes(new CustomAttributeProvider(xmlAttributes)));
return this;
}
/// <summary>
/// Creates an XmlSerializerConfigurator that is tied to the main one
/// </summary>
/// <typeparam name="K"></typeparam>
/// <returns></returns>
public XmlSerializerConfigurator<K> ChildClassConfigurator<K>()
{
// passes in own XmlAttributeOverrides and since object reference it will fill the same object
return new XmlSerializerConfigurator<K>(_xmlAttributeOverrides);
}
/// <summary>
/// Returns back an XmlSerializer with this configuration
/// </summary>
/// <returns></returns>
public XmlSerializer GetSerializer()
{
return new XmlSerializer(typeof(T), _xmlAttributeOverrides);
}
}
This class will allow you to configure what attributes are on what class/property for the Serializer and not rely on the buddy class. We need the ChildClassAttributes for when the class has another class as it's property to allow configuring that class.
Also I use an extension method that returns back a property or field from an expression called GetName.
public static string GetName<TEntity, TData>(this Expression<Func<TEntity, TData>> field)
{
var name = "";
if (field.Body is MemberExpression)
{
var body = field.Body as MemberExpression;
var ebody = body.Expression as MemberExpression;
if (ebody != null)
{
name = ebody.Member.Name + ".";
}
name = name + body.Member.Name;
}
else if (field.Body is UnaryExpression)
{
var ubody = field.Body as UnaryExpression;
var body = ubody.Operand as MemberExpression;
var ebody = body.Expression as MemberExpression;
if (ebody != null)
{
name = ebody.Member.Name + ".";
}
name = name + body.Member.Name;
}
else if (field.Body is ConstantExpression)
{
var cbody = field.Body as ConstantExpression;
name = cbody.Value.ToString();
}
else
{
throw new InvalidOperationException(String.Format("{0} not supported.", field));
}
return name;
}
Now you can use/configure it this way
var xmlConfiguration = new XmlSerializerConfigurator<Group>();
xmlConfiguration.AddPropertyOrFieldAttributes(x => x.Employees, new XmlArrayItemAttribute(typeof (Employee)),
new XmlArrayItemAttribute(typeof (Manager)));
xmlConfiguration.AddClassLevelAttributes(new XmlRootAttribute("GroupName"));
var childConfiguration = xmlConfiguration.ChildClassConfigurator<Employee>();
childConfiguration.AddPropertyOrFieldAttributes(x => x.FullName, new XmlElementAttribute("Name"));
var xmlSerializer = xmlConfiguration.GetSerializer();
Now all the properties and fields are strongly typed and not duplicated in a buddy class.