I have following JSON string which is received from an external party.
{
"team":[
{
"v1":"",
"attributes":{
"eighty_min_score":"",
"home_or_away":"home",
"score":"22",
"team_id":"500"
}
},
{
"v1":"",
"attributes":{
"eighty_min_score":"",
"home_or_away":"away",
"score":"30",
"team_id":"600"
}
}
]
}
My mapping classes:
public class Attributes
{
public string eighty_min_score { get; set; }
public string home_or_away { get; set; }
public string score { get; set; }
public string team_id { get; set; }
}
public class Team
{
public string v1 { get; set; }
public Attributes attributes { get; set; }
}
public class RootObject
{
public List<Team> team { get; set; }
}
The question is that I don't like the Attributes class name and the attributes field names in the Team class. Instead, I want it to be named TeamScore and also to remove _ from the field names and give proper names.
JsonConvert.DeserializeObject<RootObject>(jsonText);
I can rename Attributes to TeamScore, but if I change the field name (attributes in the Team class), it won't deserialize properly and gives me null. How can I overcome this?
Json.NET - Newtonsoft has a JsonPropertyAttribute which allows you to specify the name of a JSON property, so your code should be:
public class TeamScore
{
[JsonProperty("eighty_min_score")]
public string EightyMinScore { get; set; }
[JsonProperty("home_or_away")]
public string HomeOrAway { get; set; }
[JsonProperty("score ")]
public string Score { get; set; }
[JsonProperty("team_id")]
public string TeamId { get; set; }
}
public class Team
{
public string v1 { get; set; }
[JsonProperty("attributes")]
public TeamScore TeamScores { get; set; }
}
public class RootObject
{
public List<Team> Team { get; set; }
}
Documentation: Serialization Attributes
If you'd like to use dynamic mapping, and don't want to clutter up your model with attributes, this approach worked for me
Usage:
var settings = new JsonSerializerSettings();
settings.DateFormatString = "YYYY-MM-DD";
settings.ContractResolver = new CustomContractResolver();
this.DataContext = JsonConvert.DeserializeObject<CountResponse>(jsonString, settings);
Logic:
public class CustomContractResolver : DefaultContractResolver
{
private Dictionary<string, string> PropertyMappings { get; set; }
public CustomContractResolver()
{
this.PropertyMappings = new Dictionary<string, string>
{
{"Meta", "meta"},
{"LastUpdated", "last_updated"},
{"Disclaimer", "disclaimer"},
{"License", "license"},
{"CountResults", "results"},
{"Term", "term"},
{"Count", "count"},
};
}
protected override string ResolvePropertyName(string propertyName)
{
string resolvedName = null;
var resolved = this.PropertyMappings.TryGetValue(propertyName, out resolvedName);
return (resolved) ? resolvedName : base.ResolvePropertyName(propertyName);
}
}
Adding to Jacks solution. I need to Deserialize using the JsonProperty and Serialize while ignoring the JsonProperty (or vice versa). ReflectionHelper and Attribute Helper are just helper classes that get a list of properties or attributes for a property. I can include if anyone actually cares. Using the example below you can serialize the viewmodel and get "Amount" even though the JsonProperty is "RecurringPrice".
/// <summary>
/// Ignore the Json Property attribute. This is usefule when you want to serialize or deserialize differently and not
/// let the JsonProperty control everything.
/// </summary>
/// <typeparam name="T"></typeparam>
public class IgnoreJsonPropertyResolver<T> : DefaultContractResolver
{
private Dictionary<string, string> PropertyMappings { get; set; }
public IgnoreJsonPropertyResolver()
{
this.PropertyMappings = new Dictionary<string, string>();
var properties = ReflectionHelper<T>.GetGetProperties(false)();
foreach (var propertyInfo in properties)
{
var jsonProperty = AttributeHelper.GetAttribute<JsonPropertyAttribute>(propertyInfo);
if (jsonProperty != null)
{
PropertyMappings.Add(jsonProperty.PropertyName, propertyInfo.Name);
}
}
}
protected override string ResolvePropertyName(string propertyName)
{
string resolvedName = null;
var resolved = this.PropertyMappings.TryGetValue(propertyName, out resolvedName);
return (resolved) ? resolvedName : base.ResolvePropertyName(propertyName);
}
}
Usage:
var settings = new JsonSerializerSettings();
settings.DateFormatString = "YYYY-MM-DD";
settings.ContractResolver = new IgnoreJsonPropertyResolver<PlanViewModel>();
var model = new PlanViewModel() {Amount = 100};
var strModel = JsonConvert.SerializeObject(model,settings);
Model:
public class PlanViewModel
{
/// <summary>
/// The customer is charged an amount over an interval for the subscription.
/// </summary>
[JsonProperty(PropertyName = "RecurringPrice")]
public double Amount { get; set; }
/// <summary>
/// Indicates the number of intervals between each billing. If interval=2, the customer would be billed every two
/// months or years depending on the value for interval_unit.
/// </summary>
public int Interval { get; set; } = 1;
/// <summary>
/// Number of free trial days that can be granted when a customer is subscribed to this plan.
/// </summary>
public int TrialPeriod { get; set; } = 30;
/// <summary>
/// This indicates a one-time fee charged upfront while creating a subscription for this plan.
/// </summary>
[JsonProperty(PropertyName = "SetupFee")]
public double SetupAmount { get; set; } = 0;
/// <summary>
/// String representing the type id, usually a lookup value, for the record.
/// </summary>
[JsonProperty(PropertyName = "TypeId")]
public string Type { get; set; }
/// <summary>
/// Billing Frequency
/// </summary>
[JsonProperty(PropertyName = "BillingFrequency")]
public string Period { get; set; }
/// <summary>
/// String representing the type id, usually a lookup value, for the record.
/// </summary>
[JsonProperty(PropertyName = "PlanUseType")]
public string Purpose { get; set; }
}
Expanding Rentering.com's answer, in scenarios where a whole graph of many types is to be taken care of, and you're looking for a strongly typed solution, this class can help, see usage (fluent) below. It operates as either a black-list or white-list per type. A type cannot be both (Gist - also contains global ignore list).
public class PropertyFilterResolver : DefaultContractResolver
{
const string _Err = "A type can be either in the include list or the ignore list.";
Dictionary<Type, IEnumerable<string>> _IgnorePropertiesMap = new Dictionary<Type, IEnumerable<string>>();
Dictionary<Type, IEnumerable<string>> _IncludePropertiesMap = new Dictionary<Type, IEnumerable<string>>();
public PropertyFilterResolver SetIgnoredProperties<T>(params Expression<Func<T, object>>[] propertyAccessors)
{
if (propertyAccessors == null) return this;
if (_IncludePropertiesMap.ContainsKey(typeof(T))) throw new ArgumentException(_Err);
var properties = propertyAccessors.Select(GetPropertyName);
_IgnorePropertiesMap[typeof(T)] = properties.ToArray();
return this;
}
public PropertyFilterResolver SetIncludedProperties<T>(params Expression<Func<T, object>>[] propertyAccessors)
{
if (propertyAccessors == null)
return this;
if (_IgnorePropertiesMap.ContainsKey(typeof(T))) throw new ArgumentException(_Err);
var properties = propertyAccessors.Select(GetPropertyName);
_IncludePropertiesMap[typeof(T)] = properties.ToArray();
return this;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var properties = base.CreateProperties(type, memberSerialization);
var isIgnoreList = _IgnorePropertiesMap.TryGetValue(type, out IEnumerable<string> map);
if (!isIgnoreList && !_IncludePropertiesMap.TryGetValue(type, out map))
return properties;
Func<JsonProperty, bool> predicate = jp => map.Contains(jp.PropertyName) == !isIgnoreList;
return properties.Where(predicate).ToArray();
}
string GetPropertyName<TSource, TProperty>(
Expression<Func<TSource, TProperty>> propertyLambda)
{
if (!(propertyLambda.Body is MemberExpression member))
throw new ArgumentException($"Expression '{propertyLambda}' refers to a method, not a property.");
if (!(member.Member is PropertyInfo propInfo))
throw new ArgumentException($"Expression '{propertyLambda}' refers to a field, not a property.");
var type = typeof(TSource);
if (!type.GetTypeInfo().IsAssignableFrom(propInfo.DeclaringType.GetTypeInfo()))
throw new ArgumentException($"Expresion '{propertyLambda}' refers to a property that is not from type '{type}'.");
return propInfo.Name;
}
}
Usage:
var resolver = new PropertyFilterResolver()
.SetIncludedProperties<User>(
u => u.Id,
u => u.UnitId)
.SetIgnoredProperties<Person>(
r => r.Responders)
.SetIncludedProperties<Blog>(
b => b.Id)
.Ignore(nameof(IChangeTracking.IsChanged)); //see gist
I am using JsonProperty attributes when serializing but ignoring them when deserializing using this ContractResolver:
public class IgnoreJsonPropertyContractResolver: DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var properties = base.CreateProperties(type, memberSerialization);
foreach (var p in properties) { p.PropertyName = p.UnderlyingName; }
return properties;
}
}
The ContractResolver just sets every property back to the class property name (simplified from Shimmy's solution). Usage:
var airplane= JsonConvert.DeserializeObject<Airplane>(json,
new JsonSerializerSettings { ContractResolver = new IgnoreJsonPropertyContractResolver() });
Also if you want to ignore something use this
[JsonIgnore]
public int Id { get; set; }
[JsonProperty("id")]
Public string real_id { get; set; }
Related
Some of my actions accept models like:
public class PaymentRequest
{
public decimal Amount { get; set; }
public bool? SaveCard { get; set; }
public int? SmsCode { get; set; }
public BankCardDetails Card { get; set; }
}
public class BankCardDetails
{
public string Number { get; set; }
public string HolderName { get; set; }
public string ExpiryDate { get; set; }
public string ValidationCode { get; set; }
}
And the action method looks like:
[HttpPost]
[Route("api/v1/payment/pay")]
public Task<BankCardActionResponse> Pay([FromBody] PaymentRequest request)
{
if (request == null)
throw new HttpResponseException(HttpStatusCode.BadRequest);
return _paymentService.PayAsync(DataUserHelper.PhoneNumber, request);
}
I use Nlog. I think it's clear this is a bad idea to log all this bank data. My log config file contained the following line:
<attribute name="user-requestBody" layout="${aspnet-request-posted-body}"/>
I logged the request. I decided to refactor that and planned the following strategy. Actions that contain sensitive data into their requests I will mark with an attribute like
[RequestMethodFormatter(typeof(PaymentRequest))]
then take a look at my custom renderer:
[LayoutRenderer("http-request")]
public class NLogHttpRequestLayoutRenderer : AspNetRequestPostedBody
{
protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
{
base.DoAppend(builder, logEvent);
var body = builder.ToString();
// Get attribute of the called action.
var type = ... // How can I get "PaymentRequest" from the [RequestMethodFormatter(typeof(PaymentRequest))]
var res = MaskHelper.GetMaskedJsonString(body, type);
// ... and so on
}
}
I think you understand the idea. I need the type from the method's RequestMethodFormatter attribute. Is it even possible to get it into the renderer? I need it because I'm going to deserialize request JSON into particular models (it's gonna be into the MaskHelper.GetMaskedJsonString), work with the models masking the data, serialize it back into JSON.
So, did I choose a wrong approach? Or it's possible to get the type from the attribute into the renderer?
After some research, I ended up with the following solution:
namespace ConsoleApp7
{
internal class Program
{
private static void Main()
{
var sourceJson = GetSourceJson();
var userInfo = JsonConvert.DeserializeObject(sourceJson, typeof(User));
Console.WriteLine("----- Serialize without Resolver-----");
Console.WriteLine(JsonConvert.SerializeObject(userInfo));
Console.WriteLine("----- Serialize with Resolver-----");
Console.WriteLine(JsonConvert.SerializeObject(userInfo, new JsonSerializerSettings
{
ContractResolver = new MaskPropertyResolver()
}));
}
private static string GetSourceJson()
{
var guid = Guid.Parse("3e92f0c4-55dc-474b-ae21-8b3dac1a0942");
return JsonConvert.SerializeObject(new User
{
UserId = guid,
Age = 19,
Name = "John",
BirthDate = new DateTime(1990, 5, 12),
Hobbies = new[]
{
new Hobby
{
Name = "Football",
Rating = 5,
DurationYears = 3,
},
new Hobby
{
Name = "Basketball",
Rating = 7,
DurationYears = 4,
}
}
});
}
}
public class User
{
[MaskGuidValue]
public Guid UserId { get; set; }
[MaskStringValue("***")] public string Name { get; set; }
public int Age { get; set; }
[MaskDateTimeValue]
public DateTime BirthDate { get; set; }
public Hobby[] Hobbies { get; set; }
}
public class Hobby
{
[MaskStringValue("----")]
public string Name { get; set; }
[MaskIntValue(replacement: 11111)]
public int Rating { get; set; }
public int DurationYears { get; set; }
}
public class MaskPropertyResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var props = base.CreateProperties(type, memberSerialization);
var allowedPropertyTypes = new Type[]
{
typeof(Guid),
typeof(DateTime),
typeof(string),
typeof(int),
};
foreach (var prop in props.Where(p => allowedPropertyTypes.Contains(p.PropertyType)))
{
if (prop.UnderlyingName == null)
continue;
var propertyInfo = type.GetProperty(prop.UnderlyingName);
var attribute =
propertyInfo?.GetCustomAttributes().FirstOrDefault(x => x is IMaskAttribute) as IMaskAttribute;
if (attribute == null)
{
continue;
}
if (attribute.Type != propertyInfo.PropertyType)
{
// Log this case, cause somebody used wrong attribute
continue;
}
prop.ValueProvider = new MaskValueProvider(propertyInfo, attribute.Replacement, attribute.Type);
}
return props;
}
private class MaskValueProvider : IValueProvider
{
private readonly PropertyInfo _targetProperty;
private readonly object _replacement;
private readonly Type _type;
public MaskValueProvider(PropertyInfo targetProperty, object replacement, Type type)
{
_targetProperty = targetProperty;
_replacement = replacement;
_type = type;
}
public object GetValue(object target)
{
return _replacement;
}
public void SetValue(object target, object value)
{
_targetProperty.SetValue(target, value);
}
}
}
[AttributeUsage(AttributeTargets.Property)]
public class MaskStringValueAttribute : Attribute, IMaskAttribute
{
public Type Type => typeof(string);
public object Replacement { get; }
public MaskStringValueAttribute(string replacement)
{
Replacement = replacement;
}
}
[AttributeUsage(AttributeTargets.Property)]
public class MaskIntValueAttribute : Attribute, IMaskAttribute
{
public object Replacement { get; }
public Type Type => typeof(int);
public MaskIntValueAttribute(int replacement)
{
Replacement = replacement;
}
}
[AttributeUsage(AttributeTargets.Property)]
public class MaskGuidValueAttribute : Attribute, IMaskAttribute
{
public Type Type => typeof(Guid);
public object Replacement => Guid.Empty;
}
[AttributeUsage(AttributeTargets.Property)]
public class MaskDateTimeValueAttribute : Attribute, IMaskAttribute
{
public Type Type => typeof(DateTime);
public object Replacement => new DateTime(1970, 1, 1);
}
public interface IMaskAttribute
{
Type Type { get; }
object Replacement { get; }
}
}
I hope somebody will find it helpful.
You can try nuget package https://www.nuget.org/packages/Slin.Masking and https://www.nuget.org/packages/Slin.Masking.NLog.
It can easily be integrated with DotNet projects with slight changes, and you can define your rules for it. But the document needs some improvement.
As a suggestion, you can use two files:
masking.json (can be a generic one, that shared across all projects)
masking.custom.json (can be used with particular rules for specific projects)
I am trying to serialize model graph using newtonsoft.json, and I know that using ignore attribute or using custom ContractResolver I can ignore specific properties from being serialized?
Here's the custom ContractResolver I am using to ignore properties:
public class PropertyIgnoreSerializerContractResolver : DefaultContractResolver
{
private readonly Dictionary<Type, HashSet<string>> _ignores;
public PropertyIgnoreSerializerContractResolver()
{
_ignores = new Dictionary<Type, HashSet<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);
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (IsIgnored(property.DeclaringType, property.PropertyName))
property.ShouldSerialize = i => false;
return property;
}
private bool IsIgnored(Type type, string jsonPropertyName)
{
if (!_ignores.ContainsKey(type))
return false;
return _ignores[type].Contains(jsonPropertyName);
}
}
But in my case I need to include specific properties inside the graph model to be serialized rather than exclude a lot of properties inside that graph?
Is there any way to configure it to serialize specific properties?
I have modified PropertyIgnoreSerializerContractResolver a little bit to reverse the logic to include properties rather than ignoring them.
First of all imagine we have these classes:
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public List<Course> Courses { get; set; }
}
public class Course
{
public int Id { get; set; }
public string Name { get; set; }
public Person Teacher { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime? BOD { get; set; }
public decimal Salary { get; set; }
}
Serializing class and my custom ContractResolver to include properties to be serialized they look like this:
public static class SerilizationExtensions
{
public static IContractResolver contractResolver { get; set; }
public static string ToJson(this object obj, IContractResolver contractResolver=null)
{
return JsonConvert.SerializeObject(obj, new JsonSerializerSettings()
{
PreserveReferencesHandling = PreserveReferencesHandling.All,
ContractResolver =contractResolver==null? new PropertyIgnoreSerializerContractResolver():contractResolver
});
}
}
public class PropertyIncludeSerializerContractResolver : DefaultContractResolver
{
private readonly Dictionary<Type, HashSet<string>> _includedProperties;
public PropertyIncludeSerializerContractResolver()
{
_includedProperties = new Dictionary<Type, HashSet<string>>();
}
public void IncludeProperty(Type type, params string[] jsonPropertyNames)
{
if (!_includedProperties.ContainsKey(type))
_includedProperties[type] = new HashSet<string>();
foreach (var prop in jsonPropertyNames)
_includedProperties[type].Add(prop);
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (IsIncluded(property.DeclaringType, property.PropertyName) || property.PropertyType.IsValueType || property.PropertyType==typeof(string))
property.ShouldSerialize = i => true;
else
property.ShouldSerialize = i => false;
return property;
}
private bool IsIncluded(Type type, string jsonPropertyName)
{
if (!_includedProperties.ContainsKey(type))
return false;
return _includedProperties[type].Contains(jsonPropertyName);
}
}
Inside Main method :
static void Main(string[] args)
{
var student = new Student
{
Id=1,
Name="Simple Code",
Courses=new List<Course> {
new Course{ Id=1, Name="history", Teacher=new Person{Id=1,Name="James",BOD=DateTime.UtcNow,Salary=1000.50M } },
new Course{ Id=2, Name="Math", Teacher=new Person{Id=2,Name="David",BOD=DateTime.UtcNow,Salary=6000.50M } }
}
};
var jsonResolver = new PropertyIncludeSerializerContractResolver();
jsonResolver.IncludeProperty(typeof(Student), "Courses");
// if you want Teacher property to get serialized uncomment this code
//jsonResolver.IncludeProperty(typeof(Course), "Teacher");
var jsonStr = student.ToJson(jsonResolver);
Console.WriteLine(jsonStr);
Console.ReadLine();
}
So here PropertyIncludeSerializerContractResolver will ignore all reference types which are not specified to be serialized.
I hope my code will help others.
Sample class:
public class ClassA
{
public int Id { get; set; }
public string SomeString { get; set; }
public int? SomeInt { get; set; }
}
Default deserializer:
var myObject = JsonConvert.DeserializeObject<ClassA>(str);
Create the same object for two different inputs
{"Id":5}
or
{"Id":5,"SomeString":null,"SomeInt":null}
How can I track properties that were missing during deserialization process and preserve the same behavior? Is there a way to override some of JSON.net serializer methods (e.g. DefaultContractResolver class methods) to achive this. For example:
List<string> missingProps;
var myObject = JsonConvert.DeserializeObject<ClassA>(str, settings, missingProps);
For the first input list should contains the names of the missing properties ("SomeString", "SomeInt") and for second input it should be empty. Deserialized object remains the same.
1. JSON has a property which is missing in your class
Using property JsonSerializerSettings.MissingMemberHandling you can say whether missing properties are handled as errors.
Than you can install the Error delegate which will register errors.
This will detect if there is some "garbage" property in JSON string.
public class ClassA
{
public int Id { get; set; }
public string SomeString { get; set; }
}
internal class Program
{
private static void Main(string[] args)
{
const string str = "{'Id':5, 'FooBar': 42 }";
var myObject = JsonConvert.DeserializeObject<ClassA>(str
, new JsonSerializerSettings
{
Error = OnError,
MissingMemberHandling = MissingMemberHandling.Error
});
Console.ReadKey();
}
private static void OnError(object sender, ErrorEventArgs args)
{
Console.WriteLine(args.ErrorContext.Error.Message);
args.ErrorContext.Handled = true;
}
}
2. Your class has a property which is missing in JSON
Option 1:
Make it a required property:
public class ClassB
{
public int Id { get; set; }
[JsonProperty(Required = Required.Always)]
public string SomeString { get; set; }
}
Option 2:
Use some "special" value as a default value and check afterwards.
public class ClassB
{
public int Id { get; set; }
[DefaultValue("NOTSET")]
public string SomeString { get; set; }
public int? SomeInt { get; set; }
}
internal class Program
{
private static void Main(string[] args)
{
const string str = "{ 'Id':5 }";
var myObject = JsonConvert.DeserializeObject<ClassB>(str
, new JsonSerializerSettings
{
DefaultValueHandling = DefaultValueHandling.Populate
});
if (myObject.SomeString == "NOTSET")
{
Console.WriteLine("no value provided for property SomeString");
}
Console.ReadKey();
}
}
Option 3:
Another good idea would be to encapsulate this check iside the class istself. Create a Verify() method as shown below and call it after deserialization.
public class ClassC
{
public int Id { get; set; }
[DefaultValue("NOTSET")]
public string SomeString { get; set; }
public int? SomeInt { get; set; }
public void Verify()
{
if (SomeInt == null ) throw new JsonSerializationException("SomeInt not set!");
if (SomeString == "NOTSET") throw new JsonSerializationException("SomeString not set!");
}
}
Another way to find null/undefined tokens during De-serialization is to write a custom JsonConverter , Here is an example of custom converter which can report both omitted tokens (e.g. "{ 'Id':5 }") and null tokens (e.g {"Id":5,"SomeString":null,"SomeInt":null})
public class NullReportConverter : JsonConverter
{
private readonly List<PropertyInfo> _nullproperties=new List<PropertyInfo>();
public bool ReportDefinedNullTokens { get; set; }
public IEnumerable<PropertyInfo> NullProperties
{
get { return _nullproperties; }
}
public void Clear()
{
_nullproperties.Clear();
}
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
existingValue = existingValue ?? Activator.CreateInstance(objectType, true);
var jObject = JObject.Load(reader);
var properties =
objectType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var property in properties)
{
var jToken = jObject[property.Name];
if (jToken == null)
{
_nullproperties.Add(property);
continue;
}
var value = jToken.ToObject(property.PropertyType);
if(ReportDefinedNullTokens && value ==null)
_nullproperties.Add(property);
property.SetValue(existingValue, value, null);
}
return existingValue;
}
//NOTE: we can omit writer part if we only want to use the converter for deserializing
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var objectType = value.GetType();
var properties =
objectType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
writer.WriteStartObject();
foreach (var property in properties)
{
var propertyValue = property.GetValue(value, null);
writer.WritePropertyName(property.Name);
serializer.Serialize(writer, propertyValue);
}
writer.WriteEndObject();
}
}
Note: we can omit the Writer part if we don't need to use it for serializing objects.
Usage Example:
class Foo
{
public int Id { get; set; }
public string SomeString { get; set; }
public int? SomeInt { get; set; }
}
class Program
{
static void Main(string[] args)
{
var nullConverter=new NullReportConverter();
Console.WriteLine("Pass 1");
var obj0 = JsonConvert.DeserializeObject<Foo>("{\"Id\":5, \"Id\":5}", nullConverter);
foreach(var p in nullConverter.NullProperties)
Console.WriteLine(p);
nullConverter.Clear();
Console.WriteLine("Pass2");
var obj1 = JsonConvert.DeserializeObject<Foo>("{\"Id\":5,\"SomeString\":null,\"SomeInt\":null}" , nullConverter);
foreach (var p in nullConverter.NullProperties)
Console.WriteLine(p);
nullConverter.Clear();
nullConverter.ReportDefinedNullTokens = true;
Console.WriteLine("Pass3");
var obj2 = JsonConvert.DeserializeObject<Foo>("{\"Id\":5,\"SomeString\":null,\"SomeInt\":null}", nullConverter);
foreach (var p in nullConverter.NullProperties)
Console.WriteLine(p);
}
}
I got this problem, but defaultValue was not solution due to POCO object. I think this is simpler approach than NullReportConverter.
There are three unit tests. Root is class that encapsulate whole json. Key is type of the Property. Hope this helps someone.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
namespace SomeNamespace {
[TestClass]
public class NullParseJsonTest {
[TestMethod]
public void TestMethod1()
{
string slice = "{Key:{guid:\"asdf\"}}";
var result = JsonConvert.DeserializeObject<Root>(slice);
Assert.IsTrue(result.OptionalKey.IsSet);
Assert.IsNotNull(result.OptionalKey.Value);
Assert.AreEqual("asdf", result.OptionalKey.Value.Guid);
}
[TestMethod]
public void TestMethod2()
{
string slice = "{Key:null}";
var result = JsonConvert.DeserializeObject<Root>(slice);
Assert.IsTrue(result.OptionalKey.IsSet);
Assert.IsNull(result.OptionalKey.Value);
}
[TestMethod]
public void TestMethod3()
{
string slice = "{}";
var result = JsonConvert.DeserializeObject<Root>(slice);
Assert.IsFalse(result.OptionalKey.IsSet);
Assert.IsNull(result.OptionalKey.Value);
}
}
class Root {
public Key Key {
get {
return OptionalKey.Value;
}
set {
OptionalKey.Value = value;
OptionalKey.IsSet = true; // This does the trick, it is never called by JSON.NET if attribute missing
}
}
[JsonIgnore]
public Optional<Key> OptionalKey = new Optional<Key> { IsSet = false };
};
class Key {
public string Guid { get; set; }
}
class Optional<T> {
public T Value { get; set; }
public bool IsSet { get; set; }
}
}
I have a problem with the Names of my deserialized object, which I want to return.
At the moment I am writing a WebService, which requests data from another WebService and then returns this Data to an Application.
Example:
public class UserController
{
public HttpResponseMessage GetUser()
{
// Get Data from WebService
string jsonString = #"{'key': 'A45', 'field1': 'John', 'field2': 'Doe', address{'field3': 'HelloWorld Ave.', 'field4': 'Somewhere'}}";
// Make Object from JSON-String
User user = JsonConvert.DeserializeObject<User>(jsonString);
// Return Object to Application
return Request.CreateResponse(HttpStatusCode.OK, user);
}
}
public class User
{
[JsonProperty("key")]
public string Key { get; set; }
[JsonProperty("field1")]
public string FirstName { get; set; }
[JsonProperty("field2")]
public string LastName { get; set; }
[JsonProperty("address")]
public Address Address { get; set; }
}
public class Address
{
[JsonProperty("field3")]
public string Street { get; set; }
[JsonProperty("field4")]
public string City { get; set; }
}
So far so good. My WebService creates the Object "User" and returns it to the Application.
And now my problem:
The returning JSON string changes the field names back to its original name.
Instead of:
"{'key': 'A45', 'field1': 'John', 'field2': 'Doe', Address {'Street': 'HelloWorld Ave.', 'City': 'Somewhere'}}"
I get:
"{'key': 'A45', 'field1': 'John', 'field2': 'Doe', Address {'Street': 'HelloWorld Ave.', 'City': 'Somewhere'}}"
I know that the PropertyName in JsonProperty is not Case-Sensitive, so I could write [JsonProperty("Key")] with an uppercase "K" and the returning Json-String will contain the Uppercase "K".
But what about my fields? Is there any way I can change "field1" to "FirstName", and "field2" to "LastName"?
Edit 1 - 2015-01-28: Added more code to the example.
I would suggest to perform 2 steps
You (de)serialize your DTO (Data Transfer Object) as it is
You implement an assembly layer where you control the creation of your domain objects.
In your case it seems that it's a 1:1 relation between DTO and DomainModel with just different property names - so you only need to project it that way. I can highly recommend Automapper Projections or in case it's nested Automapper Nested Projections for this.
The big advantage here is that you decouple your whole domain layer from your external services, so even if your webservices gets broken / changes, it doesn't affect your business logic.
Both of #stefankmitph and #nhaberl answers were good answers, but I used a slightly different technic in the end.
Both of my classes now have private fields, for setting the value of the JSON-String to public-fields.
The private fields get the PropertyName of the incoming JSON-String and the public-fields get the PropertyName for the outgoing JSON-String:
public class User
{
#region private
[JsonProperty("key")]
private string _Key { set { Key = value; } }
[JsonProperty("field1")]
private string _FirstName { set { FirstName = value; } }
[JsonProperty("field2")]
private string _LastName { set { LastName = value; } }
[JsonProperty("address")]
private Address _Address { set { Address = value; } }
#endregion
#region public
[JsonProperty("Key")]
public string Key { get; private set; }
[JsonProperty("FirstName")]
public string FirstName { get; private set; }
[JsonProperty("LastName")]
public string LastName { get; private set; }
[JsonProperty("Address")]
public Address Address { get; private set; }
#endregion
}
public class Address
{
#region private
[JsonProperty("field3")]
private string _Street { set { Key = value; } }
[JsonProperty("field4")]
private string _City { set { FirstName = value; } }
#endregion
#region public
[JsonProperty("Street")]
public string Street { get; private set; }
[JsonProperty("City")]
public string City { get; private set; }
#endregion
}
Example output:
"{'Key': 'A45', 'FirstName': 'John', 'LastName': 'Doe', Address {'Street': 'HelloWorld Ave.', 'City': 'Somewhere'}}"
With this method I could also get all Data from the unerlying classes to the main class:
public class User
{
#region private
// Other fields, see above. \\
[JsonProperty("address")]
private Address _Address
{
set
{
City = value.City;
Street = value.Street;
}
}
#endregion
#region public
// Other fields, see above. \\
[JsonProperty("City")]
public string City { get; private set; }
[JsonProperty("Street")]
public string Street { get; private set; }
#endregion
}
public class Address
{
// Address fields. See above.
}
Example output:
"{'Key': 'A45', 'FirstName': 'John', 'LastName': 'Doe', 'Street': 'HelloWorld Ave.', 'City': 'Somewhere'}"
By writing your own serializer and adding a custom attribute to the properties you can force the serializer to take custom names when serializing back to Json.
public class JsonSerializeProperty : Attribute
{
public string PropertyName { get; set; }
}
// derive from JsonConverter and override WriteJson (serialize)
// and ReadJson (deserialize)
// note: i have not implemented CanConvert
public class CustomUserSerializer : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var user = value as User;
if(user == null)
throw new NullReferenceException("user");
var properties = user.GetType().GetProperties();
writer.WriteStartObject();
foreach (var property in properties)
{
// get the attribute assigned to the property [JsonSerializeProperty]
object customAttributes = property.GetCustomAttributes(typeof(JsonSerializeProperty), false).SingleOrDefault();
JsonSerializeProperty attribute = customAttributes as JsonSerializeProperty;
if(attribute != null)
{
// JsonSerializeProperty
string propertyName = attribute.PropertyName;
// just write new property name and value
writer.WritePropertyName(propertyName);
writer.WriteValue(property.GetValue(value, new object[] {}));
}
}
writer.WriteEndObject();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// just map every JProperty from the Json string to the
// JsonProperty of the user class. I know this is kind of ugly... but it may serve your purpose
JObject jsonObject = JObject.Load(reader);
List<JProperty> properties = jsonObject.Properties().ToList();
// create an instance of User to assign the values of the
// Json string
object instance = Activator.CreateInstance(objectType);
// loop through the user properties and get the
// JsonProperty customattribute. then set the value of the JProperty
PropertyInfo[] objectProperties = objectType.GetProperties();
foreach (var objectProperty in objectProperties)
{
var customAttribute = objectProperty.GetCustomAttributes(typeof(JsonPropertyAttribute), false).SingleOrDefault();
JsonPropertyAttribute attribute = customAttribute as JsonPropertyAttribute;
if (attribute != null)
{
JProperty jsonProperty = properties.SingleOrDefault(prop => prop.Name == attribute.PropertyName);
if (jsonProperty != null)
{
objectProperty.SetValue(instance, jsonProperty.Value.ToString(), new object[] {});
}
}
}
return instance;
// {
// _Key = "A45",
// _FirstName = "John",
// _LastName = "Doe"
// }
}
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
}
[JsonConverter(typeof(CustomUserSerializer))]
public class User
{
[JsonProperty(PropertyName = "key")]
[JsonSerializeProperty(PropertyName = "_Key")]
public string Key { get; set; }
[JsonProperty("field1")]
[JsonSerializeProperty(PropertyName = "_FirstName")]
public string FirstName { get; set; }
[JsonProperty("field2")]
[JsonSerializeProperty(PropertyName = "_LastName")]
public string LastName { get; set; }
}
I am using this method to copy properties of one object to another and its working fine.
But today i found that its not working for arrays of different object.
please help me in this.
public static class CopyClass
{
/// <summary>
/// Copies source object properties to target object properties.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="target">The target.</param>
public static void CopyTo(object source, object target)
{
foreach (PropertyInfo propSource in source.GetType().GetProperties())
{
foreach (PropertyInfo propTarget in target.GetType().GetProperties())
{
if (propTarget.Name != propSource.Name) continue;
(propTarget.GetSetMethod()).Invoke(target,
new object[] { propSource.GetGetMethod().Invoke(source, null) });
}
}
}
}
Can you check with this function? I am not sure if this is working with Arrays but it sure does with plain members.
public static object ObjectCopyProperties(this object sourceObject, object targetObject)
{
if (sourceObject == null || targetObject == null)
return null;
var targetInstance = targetObject;
PropertyInfo newProp;
foreach (PropertyInfo prop in sourceObject.GetType().GetProperties())
{
if (prop.CanRead)
{
newProp = targetInstance.GetType().GetProperty(prop.Name);
if (newProp != null && newProp.CanWrite)
{
newProp.SetValue(targetInstance, prop.GetValue(sourceObject, null), null);
}
}
}
return targetInstance;
}
Given the following 2 classes
public class UserType1
{
public DateTime Created { get; set; }
public string First { get; set; }
public Gender Genter { get; set; }
public int Id { get; set; }
public string Last { get; set; }
public DateTime Updated { get; set; }
public string DontMatchType { get; set; }
public string Unique1 { get; set; }
}
public class UserType2
{
public DateTime Created { get; set; }
public string First { get; set; }
public Gender Genter { get; set; }
public int Id { get; set; }
public string Last { get; set; }
public DateTime Updated { get; set; }
public int DontMatchType { get; set; }
public string Unique2 { get; set; }
}
and the following code
UserType1 user1 = new UserType1
{
Id = 1,
First = "John",
Last = "Doe",
Genter = Gender.Male,
Created = DateTime.Now.AddDays(-1),
Updated = DateTime.Now,
DontMatchType = "won't map",
Unique1 = "foobar"
};
UserType2 user2 = CopyTo<UserType2>(user1);
you can see that this mapping function will only map matching name/types
public static T CopyTo<T>(object source)
where T : new()
{
if (source == null) throw new ArgumentException("surce is null", "source");
T target = new T();
source.GetType()
.GetProperties()
.Join(target.GetType().GetProperties()
, s => s.Name
, t => t.Name
, (s, t) => new
{
source = s,
target = t
})
.AsParallel()
.Where(inCommon => inCommon.source.PropertyType == inCommon.target.PropertyType
&& inCommon.source.CanRead && inCommon.target.CanWrite)
.ForAll(inCommon => inCommon.target.SetValue(target, inCommon.source.GetValue(source, null), null));
return target;
}
and you can use
public static IEnumerable<T> CopyTo<T>(IEnumerable<object> source)
where T : new()
{
return source.AsParallel().Select(CopyTo<T>);
}
to copy a collection like this
UserType1[] users1 = new[]
{
new UserType1
{
...
}
};
UserType2[] users2 = CopyTo<UserType2>(users1).ToArray();
This has the added benefit that it will not needlessly loop all the properties of object B for every property in object A, as its using a join to find the properties in common (by name and type).
Mapping can be tricky. You can get into a lot of situations of name/types and nesting. I would suggest Automapper as jjchiw mentioned.
If your object is serializable you can create an extension method which serialize the source object and deserialize it in return
public static class CloneExtensions
{
public static T Clone<T>(this T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", "source");
}
if(source == default(T))
return default(T);
IFormatter formatter = new BinaryFormatter();
Stream stream = new MemoryStream();
using (stream)
{
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
}