Assuming we have a JSON object similar to:
{
'12345': 'text string',
'rel': 'myResource'
}
Constructing a DataContract to map to this type seems fairly simple such as:
[DataContract]
MyResource
{
[DataMember(Name = "12345")]
public string SpecialValue { get; set; }
[DataMember(Name = "rel")]
public string Rel { get; set; }
}
Now the problem arrives that the name of the property is variable so it not guaranteed to be '12345'. Since this variable cannot be properly mapped using attributes it won't get picked up when using DataContractJsonSerializer.
If I change the class to support IExtensibleDataObject, I can get the value portion but not the property name which is a problem. I'm looking to maintain this value during deserialization/serialization in order to be able to send the information on a return request. I'm not looking to change over to using Json.NET to solve this problem as I want to know if it is possible in some form without resorting to an external dependency.
It's a little ugly, but it turns out you can use an IDataContractSurrogate to deserialize the class with the variably named property into a Dictionary<string, object> and then copy the values from the dictionary into your class. Of course, you will need to add another property to your class to hold the name of the "special" property.
Here is an example surrogate that I was able to get working:
class MyDataContractSurrogate : IDataContractSurrogate
{
public Type GetDataContractType(Type type)
{
if (type == typeof(MyResource))
{
return typeof(Dictionary<string, object>);
}
return type;
}
public object GetDeserializedObject(object obj, Type targetType)
{
if (obj.GetType() == typeof(Dictionary<string, object>) &&
targetType == typeof(MyResource))
{
Dictionary<string, object> dict = (Dictionary<string, object>)obj;
MyResource mr = new MyResource();
foreach (PropertyInfo prop in GetInterestingProperties(typeof(MyResource)))
{
DataMemberAttribute att = prop.GetCustomAttribute<DataMemberAttribute>();
object value;
if (dict.TryGetValue(att.Name, out value))
{
prop.SetValue(mr, value);
dict.Remove(att.Name);
}
}
// should only be one property left in the dictionary
if (dict.Count > 0)
{
var kvp = dict.First();
mr.SpecialName = kvp.Key;
mr.SpecialValue = (string)kvp.Value;
}
return mr;
}
return obj;
}
public object GetObjectToSerialize(object obj, Type targetType)
{
if (obj.GetType() == typeof(MyResource) &&
targetType == typeof(Dictionary<string, object>))
{
MyResource mr = (MyResource)obj;
Dictionary<string, object> dict = new Dictionary<string, object>();
dict.Add(mr.SpecialName, mr.SpecialValue);
foreach (PropertyInfo prop in GetInterestingProperties(typeof(MyResource)))
{
DataMemberAttribute att = prop.GetCustomAttribute<DataMemberAttribute>();
dict.Add(att.Name, prop.GetValue(mr));
}
return dict;
}
return obj;
}
private IEnumerable<PropertyInfo> GetInterestingProperties(Type type)
{
return type.GetProperties().Where(p => p.CanRead && p.CanWrite &&
p.GetCustomAttribute<DataMemberAttribute>() != null);
}
// ------- The rest of these methods are not needed -------
public object GetCustomDataToExport(Type clrType, Type dataContractType)
{
throw new NotImplementedException();
}
public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
{
throw new NotImplementedException();
}
public void GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes)
{
throw new NotImplementedException();
}
public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
{
throw new NotImplementedException();
}
public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
{
throw new NotImplementedException();
}
}
To use the surrogate, you'll need to create an instance of DataContractJsonSerializerSettings and pass it to the DataContractJsonSerializer with the following properties set. Note that since we require the UseSimpleDictionaryFormat setting, this solution will only work with .Net 4.5 or later.
var settings = new DataContractJsonSerializerSettings();
settings.DataContractSurrogate = new MyDataContractSurrogate();
settings.KnownTypes = new List<Type> { typeof(Dictionary<string, object>) };
settings.UseSimpleDictionaryFormat = true;
Note also that in your class you should NOT mark the "special" properties with a [DataMember] attribute, since they are handled specially in the surrogate.
[DataContract]
class MyResource
{
// Don't mark these with [DataMember]
public string SpecialName { get; set; }
public string SpecialValue { get; set; }
[DataMember(Name = "rel")]
public string Rel { get; set; }
}
Here is a demo:
class Program
{
static void Main(string[] args)
{
string json = #"
{
""12345"": ""text string"",
""rel"": ""myResource""
}";
var settings = new DataContractJsonSerializerSettings();
settings.DataContractSurrogate = new MyDataContractSurrogate();
settings.KnownTypes = new List<Type> { typeof(Dictionary<string, object>) };
settings.UseSimpleDictionaryFormat = true;
MyResource mr = Deserialize<MyResource>(json, settings);
Console.WriteLine("Special name: " + mr.SpecialName);
Console.WriteLine("Special value: " + mr.SpecialValue);
Console.WriteLine("Rel: " + mr.Rel);
Console.WriteLine();
json = Serialize(mr, settings);
Console.WriteLine(json);
}
public static T Deserialize<T>(string json, DataContractJsonSerializerSettings settings = null)
{
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(json)))
{
if (settings == null) settings = GetDefaultSerializerSettings();
var ser = new DataContractJsonSerializer(typeof(T), settings);
return (T)ser.ReadObject(ms);
}
}
public static string Serialize(object obj, DataContractJsonSerializerSettings settings = null)
{
using (MemoryStream ms = new MemoryStream())
{
if (settings == null) settings = GetDefaultSerializerSettings();
var ser = new DataContractJsonSerializer(obj.GetType(), settings);
ser.WriteObject(ms, obj);
return Encoding.UTF8.GetString(ms.ToArray());
}
}
}
Output:
Special name: 12345
Special value: text string
Rel: myResource
{"12345":"text string","rel":"myResource"}
Related
I have the following contract:
class Information
{
public string SensitiveInformation { get; set; }
public string NotSensitiveInformation { get; set; }
public IDictionary<string, string> PartialSensitiveInformation { get; set; }
}
My goal is to serialize the class, but I need to ommit some sensitive information. for this I've created a contract resolver:
class IgnorePropertiesContractResolver : DefaultContractResolver
{
private readonly HashSet<string> propertyNamesToIgnore;
public IgnorePropertiesContractResolver(HashSet<string> propertyNamesToIgnore)
{
this.propertyNamesToIgnore = propertyNamesToIgnore;
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty jsonProperty = base.CreateProperty(member, memberSerialization);
if (this.propertyNamesToIgnore.Contains(jsonProperty.PropertyName))
{
jsonProperty.ShouldSerialize = x => false;
}
return jsonProperty;
}
}
and running the code:
IgnorePropertiesContractResolver resolver = new IgnorePropertiesContractResolver(new HashSet<string> {"SensitiveInformation" });
Information info = new Information();
info.SensitiveInformation = "sensitive data";
info.NotSensitiveInformation = "not sensitive data";
info.PartialSensitiveInformation = new Dictionary<string, string>();
info.PartialSensitiveInformation["secret_data"] = "secret data";
info.PartialSensitiveInformation["not_secret_data"] = "not secret data";
var data = JsonConvert.SerializeObject(info, new JsonSerializerSettings { ContractResolver = resolver });
Returns this data: {"NotSensitiveInformation":"not sensitive data","PartialSensitiveInformation":{"secret_data":"secret data","not_secret_data":"not secret data"}}
Hor to change my contract resolver so I can ommit fom the serialization certain keys from the dictionary PartialSensitiveInformation ? I don't want to serialize the key "secret_data".
Please note that I have the contract in a nuget so adding attribute is not possible in this case.
I'm using .net franework 4.7.2.
You can implement a custom value provider (using IValueProvider) to handle this. The value provider can take in the list of sensitive property names, and transform the dictionary to exclude the sensitive properties:
public class PartialSensitiveInformationValueProvider : IValueProvider
{
private readonly HashSet<string> _propertyNamesToIgnore;
public PartialSensitiveInformationValueProvider(HashSet<string> propertyNamesToIgnore)
{
_propertyNamesToIgnore = propertyNamesToIgnore;
}
public object GetValue(object target)
{
return ((Information)target).PartialSensitiveInformation
.Where(x => !_propertyNamesToIgnore.Contains(x.Key))
.ToDictionary(k => k.Key, v => v.Value);
}
public void SetValue(object target, object value)
{
throw new NotImplementedException();
}
}
You can then use this value provider in the contract resolver:
public class IgnorePropertiesContractResolver : DefaultContractResolver
{
private readonly HashSet<string> propertyNamesToIgnore;
public IgnorePropertiesContractResolver(HashSet<string> propertyNamesToIgnore)
{
this.propertyNamesToIgnore = propertyNamesToIgnore;
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty jsonProperty = base.CreateProperty(member, memberSerialization);
if (this.propertyNamesToIgnore.Contains(jsonProperty.PropertyName))
{
jsonProperty.ShouldSerialize = x => false;
}
if (jsonProperty.PropertyName == nameof(Information.PartialSensitiveInformation))
{
jsonProperty.ValueProvider = new PartialSensitiveInformationValueProvider(this.propertyNamesToIgnore);
}
return jsonProperty;
}
}
Using the example you have (including "secret_data" in the propertyNamesToIgnore), the results are now:
{"NotSensitiveInformation":"not sensitive data","PartialSensitiveInformation":{"not_secret_data":"not secret data"}}
My answer is a bit more involved than #devNull's, the difference being it's a generic approach:
It allows you to target specific members using an expression, e.g. s => s.Inner.Dictionary.
Sample data:
public class MyData
{
public Dictionary<string, string> Dictionary { get; set; } = new Dictionary<string, string>();
public MyDataInner Inner { get; set; } = new MyDataInner();
}
public class MyDataInner
{
public Dictionary<string, string> Dictionary { get; set; } = new Dictionary<string, string>();
}
Newtonsoft related classes, contract, converter, resolver:
public class MyJsonDictionaryContract : JsonDictionaryContract
{
public MyJsonDictionaryContract(Type underlyingType, (string, string[])[] filters) : base(underlyingType)
{
Converter = new MyJsonConverter(filters);
}
}
internal class MyJsonConverter : JsonConverter<IDictionary<string, string>>
{
public MyJsonConverter((string, string[])[] filters)
{
Filters = filters;
}
private (string, string[])[] Filters { get; }
public override void WriteJson(JsonWriter writer, IDictionary<string, string> value, JsonSerializer serializer)
{
foreach (var (k, v) in value)
{
var tuple = Filters.FirstOrDefault(s => s.Item1 == writer.Path);
if (tuple != default && tuple.Item2.Contains(k))
continue; // if Filters has path and key then NOP
writer.WriteStartObject();
writer.WritePropertyName(k);
writer.WriteValue(v);
writer.WriteEndObject();
}
}
public override IDictionary<string, string> ReadJson(
JsonReader reader,
Type objectType,
IDictionary<string, string> existingValue,
bool hasExistingValue,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public class MyDataContractResolver : DefaultContractResolver
{
public MyDataContractResolver((string, string[])[] filters)
{
Filters = filters;
}
private (string, string[])[] Filters { get; }
protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
{
return new MyJsonDictionaryContract(objectType, Filters);
}
}
The example:
internal static class Program
{
private static void Main(string[] args)
{
var data = new MyData
{
Dictionary = new Dictionary<string, string>
{
{"public", "abcd"},
{"private", "abcd"}
},
Inner = new MyDataInner
{
Dictionary = new Dictionary<string, string>
{
{"public", "abcd"},
{"private", "abcd"}
}
}
};
var filters = new[]
{
(ExpressionUtils.GetPath(data, s => s.Dictionary), new[] {"private"}),
(ExpressionUtils.GetPath(data, s => s.Inner.Dictionary), new[] {"public"})
};
var resolver = new MyDataContractResolver(filters);
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
ContractResolver = resolver
};
var json = JsonConvert.SerializeObject(data, settings);
}
}
internal static class ExpressionUtils
{
/// <summary>
/// expression 2 path, e.g. class.prop1.prop2
/// </summary>
public static string GetPath<TSource, TProperty>(TSource source, Expression<Func<TSource, TProperty>> property)
{
// https://stackoverflow.com/a/1667533/361899
var expression = property.Body.NodeType switch
{
ExpressionType.Convert => (property.Body is UnaryExpression ue ? ue.Operand : null) as MemberExpression,
ExpressionType.ConvertChecked =>
(property.Body is UnaryExpression ue ? ue.Operand : null) as MemberExpression,
_ => property.Body as MemberExpression
};
var stack = new Stack<string>();
while (expression != null)
{
stack.Push(expression.Member.Name);
expression = expression.Expression as MemberExpression;
}
var path = string.Join(".", stack);
return path;
}
}
Result without it:
{
"Dictionary": {
"public": "abcd",
"private": "abcd"
},
"Inner": {
"Dictionary": {
"public": "abcd",
"private": "abcd"
}
}
}
Result with it:
{
"Dictionary": {
"public": "abcd"
},
"Inner": {
"Dictionary": {
"private": "abcd"
}
}
}
So there you are, you can filter by key, but target any member whether it's nested or not.
This question already has answers here:
Serialize/Deserialize dynamic property name using JSON.NET
(4 answers)
Closed 2 years ago.
I need to have a dynamic property-name for the serialization.
public class Home
{
public virtual int Id { get; set; } // value: 2
public virtual string propertyName { get; set; } // value: administration
public virtual string Text { get; set; } // value: text1
}
should serialize to:
{
"Id": 2,
"administration": "text1"
}
Is there any way to serialize that? Which is the best way to deserialize it?
According to this post on how to Dynamically rename or ignore properties without changing the serialized class by Rico Suter, you can add a class which extends DefaultContractResolver named PropertyRenameAndIgnoreSerializerContractResolver.
So the model would look like:
public class Home
{
[JsonProperty("firstName")]
public int Id { get; set; } // value: 2
//public Dictionary<string,string> dictionary { get; set; }
[JsonProperty("propertyName")]
public string propertyName { get; set; } // value: administration
[JsonIgnore]
public string Text { get; set; } // value: text1
}
And serialization would look like this:
var home = new Home();
home.Id = 2;
home.propertyName = "text1";
var jsonResolver = new PropertyRenameAndIgnoreSerializerContractResolver();
jsonResolver.RenameProperty(typeof(Home), "propertyName", "administration");
var serializerSettings = new JsonSerializerSettings();
serializerSettings.ContractResolver = jsonResolver;
var json = JsonConvert.SerializeObject(home, serializerSettings);
Which give the desire output.
Add this class PropertyRenameAndIgnoreSerializerContractResolver.cs:
public class PropertyRenameAndIgnoreSerializerContractResolver : DefaultContractResolver
{
private readonly Dictionary<Type, HashSet<string>> _ignores;
private readonly Dictionary<Type, Dictionary<string, string>> _renames;
public PropertyRenameAndIgnoreSerializerContractResolver()
{
_ignores = new Dictionary<Type, HashSet<string>>();
_renames = new Dictionary<Type, Dictionary<string, string>>();
}
public void IgnoreProperty(Type type, params string[] jsonPropertyNames)
{
if (!_ignores.ContainsKey(type))
_ignores[type] = new HashSet<string>();
foreach (var prop in jsonPropertyNames)
_ignores[type].Add(prop);
}
public void RenameProperty(Type type, string propertyName, string newJsonPropertyName)
{
if (!_renames.ContainsKey(type))
_renames[type] = new Dictionary<string, string>();
_renames[type][propertyName] = newJsonPropertyName;
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (IsIgnored(property.DeclaringType, property.PropertyName))
property.ShouldSerialize = i => false;
if (IsRenamed(property.DeclaringType, property.PropertyName, out var newJsonPropertyName))
property.PropertyName = newJsonPropertyName;
return property;
}
private bool IsIgnored(Type type, string jsonPropertyName)
{
if (!_ignores.ContainsKey(type))
return false;
return _ignores[type].Contains(jsonPropertyName);
}
private bool IsRenamed(Type type, string jsonPropertyName, out string newJsonPropertyName)
{
Dictionary<string, string> renames;
if (!_renames.TryGetValue(type, out renames) || !renames.TryGetValue(jsonPropertyName, out newJsonPropertyName))
{
newJsonPropertyName = null;
return false;
}
return true;
}
}
Add a ToJObject method that returns a JObject.
public JObject ToJObject()
{
JObject jObject = new JObject()
{
{ "Id", Id },
{ propertyName, Text }
}
return jObject;
}
Then for Deserializing i would probably create a factory method something like this:
public static Home CreateFromJObject(JObject obj)
{
Home h = new Home();
foreach (var a in obj)
{
if (a.Key == "ID")
{
h.Id = a.Value.Value<int>();
}
else
{
h.propertyName = a.Key;
h.Text = a.Value.Value<string>();
}
}
return h;
}
Ofcause if you have multiple other values in there i would either change it to a switch or make sure that only the needed JObject is passed in there.
i am using Xml Attributes on my Models to handle serializing my Models.
the base class is:
public class RequestRoot<T>
{
[XmlElement("Action")]
public T ActionNode { get; set; }
}
ActionNode is of type T, and can be anything from a string to a collection of complex objects.
example:
<?xml version="1.0" encoding="UTF-8"?>
<RequestRoot>
<Action Type="TypeValue A">
<SomeData>Some data</SomeData>
</Action>
</RequestRoot>
<?xml version="1.0" encoding="UTF-8"?>
<RequestRoot>
<Action Type="TypeValue B">
<MyObjects>
<MyObject>
<ObjectValue1>Object Value 1-1</ObjectValue1>
<ObjectValue2>Object Value 2-1</ObjectValue2>
</MyObject>
<MyObject>
<ObjectValue1>Object Value 1-2</ObjectValue1>
<ObjectValue2>Object Value 2-2</ObjectValue2>
</MyObject>
</MyObjects>
</Action>
</RequestRoot>
my question is this: is it possible to use Xml Attributes on my Models to write Type="TypeValue A" or Type="TypeValue B" depending upon what T is?
if not, what alternatives do i have?
There is no way to do this with XmlSerializer out of the box. That's because your RequestRoot class is generic, and XmlSerializer determines the type of object to create based on the XML element name and, possibly, the "xsi:type" attribute. Your type information, however, is embedded in a sub-element Action of the root element which is not accessible at the time the root must needs be allocated.
What you will have to do is to read and write the RequestRoot wrapper manually, then use XmlSerializer for the contents. For instance:
public abstract class RequestRootBase
{
[XmlIgnore]
public abstract Type RequestType { get; }
[XmlIgnore]
public abstract Object RequestObject { get; }
}
public class RequestRoot<T> : RequestRootBase
{
public RequestRoot() { }
public RequestRoot(T ActionNode) { this.ActionNode = ActionNode; }
[XmlElement("Action")]
public T ActionNode { get; set; }
public override Type RequestType
{
get { return typeof(T); }
}
public override object RequestObject
{
get { return ActionNode; }
}
}
public static class RequestRootHelper
{
public static RequestRootBase CreateBase(object action)
{
if (action == null)
throw new ArgumentNullException();
var type = action.GetType();
return (RequestRootBase)Activator.CreateInstance(typeof(RequestRoot<>).MakeGenericType(type), new [] { action });
}
public static RequestRoot<T> Create<T>(T action)
{
return new RequestRoot<T> { ActionNode = action };
}
}
public abstract class RequestRootXmlSerializerBase
{
const string RequestRootElementName = "RequestRoot";
const string ActionElementName = "Action";
const string TypeAttributeName = "Type";
protected abstract Type BindToType(string name);
protected abstract string BindToName(Type type);
static string DefaultRootXmlElementNamespace(Type type)
{
var xmlType = type.GetCustomAttribute<XmlRootAttribute>();
if (xmlType != null && !string.IsNullOrEmpty(xmlType.Namespace))
return xmlType.Namespace;
return null;
}
public void Serialize(RequestRootBase root, XmlWriter writer)
{
writer.WriteStartDocument();
writer.WriteStartElement(RequestRootElementName);
writer.WriteStartElement(ActionElementName);
var typeName = BindToName(root.RequestType);
writer.WriteAttributeString(TypeAttributeName, typeName);
var serializer = new XmlSerializer(root.RequestType);
var rootNameSpace = DefaultRootXmlElementNamespace(root.RequestType);
var ns = new XmlSerializerNamespaces();
if (string.IsNullOrEmpty(rootNameSpace))
ns.Add("", "");
else
ns.Add("", rootNameSpace);
serializer.Serialize(writer, root.RequestObject, ns);
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndDocument();
}
public RequestRootBase Deserialize(XmlReader reader)
{
if (!reader.ReadToFollowing(RequestRootElementName))
return null;
if (!reader.ReadToFollowing(ActionElementName))
return null;
var typeName = reader[TypeAttributeName];
if (typeName == null)
return null;
var type = BindToType(typeName);
if (type == null)
throw new InvalidDataException(); // THROW AN EXCEPTION in this case
reader.ReadStartElement();
var serializer = new XmlSerializer(type);
var action = serializer.Deserialize(reader);
if (action == null)
return null;
return RequestRootHelper.CreateBase(action);
}
public string SerializeToString(RequestRootBase root)
{
using (var textWriter = new StringWriter())
{
var settings = new XmlWriterSettings() { Indent = true, IndentChars = " " }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
Serialize(root, xmlWriter);
return textWriter.ToString();
}
}
public RequestRootBase DeserializeFromString(string xml)
{
using (var sr = new StringReader(xml))
using (var xmlReader = XmlReader.Create(sr))
{
return Deserialize(xmlReader);
}
}
}
public class RequestRootXmlSerializer : RequestRootXmlSerializerBase
{
readonly Dictionary<string, Type> nameToType = new Dictionary<string, Type>();
readonly Dictionary<Type, string> typeToName = new Dictionary<Type, string>();
const string ListPrefix = "ArrayOf";
const string ListPostFix = "";
protected override string BindToName(Type type)
{
return typeToName[type];
}
protected override Type BindToType(string name)
{
return nameToType[name];
}
public RequestRootXmlSerializer(IEnumerable<Type> types)
{
if (types == null)
throw new ArgumentNullException();
foreach (var type in types)
{
if (type.IsInterface || type.IsAbstract)
throw new ArgumentException();
var name = DefaultXmlElementName(type);
nameToType.Add(name, type);
typeToName.Add(type, name);
}
}
static string DefaultXmlElementName(Type type)
{
if (type.IsGenericType
&& type.GetGenericTypeDefinition() == typeof(List<>))
{
var elementType = type.GetGenericArguments()[0];
return ListPrefix + DefaultXmlElementName(elementType) + ListPostFix;
}
else
{
var xmlRoot = type.GetCustomAttribute<XmlRootAttribute>();
if (xmlRoot != null && !string.IsNullOrEmpty(xmlRoot.ElementName))
return xmlRoot.ElementName;
var xmlType = type.GetCustomAttribute<XmlTypeAttribute>();
if (xmlType != null && !string.IsNullOrEmpty(xmlType.TypeName))
return xmlType.TypeName;
return type.Name;
}
}
}
You will probably want to replace my type-to-name mapping scheme with your own; it's just a prototype.
Then use it like:
[XmlRoot("A", Namespace="ATestNameSpace")]
public class ClassA
{
[XmlText]
public string Value { get; set; }
}
public class MyObject
{
public string ObjectValue1 { get; set; }
public string ObjectValue2 { get; set; }
}
public class TestClass
{
public static void Test()
{
var root1 = RequestRootHelper.Create(new ClassA { Value = "Some data" });
var root2 = RequestRootHelper.Create(new List<MyObject> { new MyObject { ObjectValue1 = "Object Value 1-1", ObjectValue2 = "Object Value 2-1" }, new MyObject { ObjectValue1 = "Object Value 1-2", ObjectValue2 = "Object Value 2-2" } });
var serializer = new RequestRootXmlSerializer(new[] { typeof(ClassA), typeof(List<ClassA>), typeof(MyObject), typeof(List<MyObject>) });
TestRootSerialization(root1, serializer);
TestRootSerialization(root2, serializer);
}
private static void TestRootSerialization<T>(RequestRoot<T> root, RequestRootXmlSerializer serializer)
{
var xml1 = serializer.SerializeToString(root);
Debug.WriteLine(xml1);
var root11 = serializer.DeserializeFromString(xml1);
Debug.Assert(root.GetType() == root11.GetType()); // NO ASSERT
var xml11 = serializer.SerializeToString(root11);
Debug.WriteLine(xml11);
Debug.Assert(xml1 == xml11); // NO ASSERT
}
}
This produces the following XML output for ClassA:
<RequestRoot>
<Action Type="A">
<A xmlns="ATestNameSpace">Some data</A>
</Action>
</RequestRoot>
And for List<MyObject>:
<RequestRoot>
<Action Type="ArrayOfMyObject">
<ArrayOfMyObject>
<MyObject>
<ObjectValue1>Object Value 1-1</ObjectValue1>
<ObjectValue2>Object Value 2-1</ObjectValue2>
</MyObject>
<MyObject>
<ObjectValue1>Object Value 1-2</ObjectValue1>
<ObjectValue2>Object Value 2-2</ObjectValue2>
</MyObject>
</ArrayOfMyObject>
</Action>
</RequestRoot>
I am working on some code that is written in C#. In this app, I have a custom collection defined as follows:
public class ResultList<T> : IEnumerable<T>
{
public List<T> Results { get; set; }
public decimal CenterLatitude { get; set; }
public decimal CenterLongitude { get; set; }
}
The type used by Results are one of three custom types. The properties of each of the custom types are just primitive types (ints, strings, bools, int?, bool?). Here is an example of one of the custom types:
public class ResultItem
{
public int ID { get; set; }
public string Name { get; set; }
public bool? isLegit { get; set; }
}
How do I perform a deep copy of a ResultList object that I've created. I found this post: Generic method to create deep copy of all elements in a collection. However, I can't figure out how to do it.
The approach involving the least coding effort is that of serializing and deserializing through a BinaryFormatter.
You could define the following extension method (taken from Kilhoffer’s answer):
public static T DeepClone<T>(T obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T)formatter.Deserialize(ms);
}
}
…and then just call:
ResultList<T> clone = DeepClone(original);
One of the reasons why your ResultList class won't work with Jon Skeet's example is because it does not implement the ICloneable interface.
Implement ICloneable on all the classes that you need cloned, e.g.
public class ResultItem : ICloneable
{
public object Clone()
{
var item = new ResultItem
{
ID = ID,
Name = Name,
isLegit = isLegit
};
return item;
}
}
And also on ResultList:
public class ResultList<T> : IEnumerable<T>, ICloneable where T : ICloneable
{
public List<T> Results { get; set; }
public decimal CenterLatitude { get; set; }
public decimal CenterLongitude { get; set; }
public object Clone()
{
var list = new ResultList<T>
{
CenterLatitude = CenterLatitude,
CenterLongitude = CenterLongitude,
Results = Results.Select(x => x.Clone()).Cast<T>().ToList()
};
return list;
}
}
Then to make a deep copy of your object:
resultList.clone();
Expanding on #Georgi-it, I had to modify his code to handle properties whose type inherits List:
public static class ObjectCloner {
public static T Clone<T>(object obj, bool deep = false) where T : new() {
if (!(obj is T)) {
throw new Exception("Cloning object must match output type");
}
return (T)Clone(obj, deep);
}
public static object Clone(object obj, bool deep) {
if (obj == null) {
return null;
}
Type objType = obj.GetType();
if (objType.IsPrimitive || objType == typeof(string) || objType.GetConstructors().FirstOrDefault(x => x.GetParameters().Length == 0) == null) {
return obj;
}
List<PropertyInfo> properties = objType.GetProperties().ToList();
if (deep) {
properties.AddRange(objType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic));
}
object newObj = Activator.CreateInstance(objType);
foreach (var prop in properties) {
if (prop.GetSetMethod() != null) {
var proceed = true;
if (obj is IList) {
var listType = obj.GetType().GetProperty("Item").PropertyType;
if (prop.PropertyType == listType) {
proceed = false;
foreach (var item in obj as IList) {
object clone = Clone(item, deep);
(newObj as IList).Add(clone);
}
}
}
if (proceed) {
object propValue = prop.GetValue(obj, null);
object clone = Clone(propValue, deep);
prop.SetValue(newObj, clone, null);
}
}
}
return newObj;
}
}
Here is something that I needed and wrote, it uses reflection to copy every property (and private ones if specified)
public static class ObjectCloner
{
public static T Clone<T>(object obj, bool deep = false) where T : new()
{
if (!(obj is T))
{
throw new Exception("Cloning object must match output type");
}
return (T)Clone(obj, deep);
}
public static object Clone(object obj, bool deep)
{
if (obj == null)
{
return null;
}
Type objType = obj.GetType();
if (objType.IsPrimitive || objType == typeof(string) || objType.GetConstructors().FirstOrDefault(x => x.GetParameters().Length == 0) == null)
{
return obj;
}
List<PropertyInfo> properties = objType.GetProperties().ToList();
if (deep)
{
properties.AddRange(objType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic));
}
object newObj = Activator.CreateInstance(objType);
foreach (var prop in properties)
{
if (prop.GetSetMethod() != null)
{
object propValue = prop.GetValue(obj, null);
object clone = Clone(propValue, deep);
prop.SetValue(newObj, clone, null);
}
}
return newObj;
}
}
For deep coping of an object you can use this code:
public static T DeepCopy<T>(T obj) {
var str = Newtonsoft.Json.JsonConvert.SerializeObject(obj);
var ret = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(str);
return ret;
}
I'm using something like:
var users = somelinqquery;
Currently, I'm returning a serialized users using:
return new JavaScriptSerializer().Serialize(
new { Result = true, Users = users }
);
The User object have more properties that I need to serialize, age, birthday, etc...
How can I choose what properties to serialize, something like:
return new JavaScriptSerializer().Serialize(
new { Result = true, Users = new { Id = users.id, Name = users.name } }
);
Add ScriptIgnore attribute to your field/property
public class User
{
[ScriptIgnore]
public string IgnoreThisField= "aaa";
public string Name = "Joe";
}
You could create your own JavaScriptConverter for this specific type.
By overriding IDictionary<string, object> Serialize(object, JavaScriptSerializer) you'll include only those values that needs to be converted.
IEnumerable<Type> SupportedTypes will ensure that only the types that you specify will be passed into this method.
Edit
I'm adding a code snippet to illustrate my point.
public class Foo {
public String FooField { get; set; }
public String NotSerializedFooString { get; set; }
}
public class FooConverter : JavaScriptConverter {
public override Object Deserialize(IDictionary<String, Object> dictionary, Type type, JavaScriptSerializer serializer) {
throw new NotImplementedException();
}
public override IDictionary<String, Object> Serialize(Object obj, JavaScriptSerializer serializer) {
// Here I'll get instances of Foo only.
var data = obj as Foo;
// Prepare a dictionary
var dic = new Dictionary<String, Object>();
// Include only those values that should be there
dic["FooField"] = data.FooField;
// return the dictionary
return dic;
}
public override IEnumerable<Type> SupportedTypes {
get {
// I return the array with only one element.
// This means that this converter will be used with instances of
// only this type.
return new[] { typeof(Foo) };
}
}
}