Exclude property from serialization via custom attribute (json.net) - c#

I need to be able to control how/whether certain properties on a class are serialized. The simplest case is [ScriptIgnore]. However, I only want these attributes to be honored for this one specific serialization situation I am working on - if other modules downstream in the application also want to serialize these objects, none of these attributes should get in the way.
So my thought is to use a custom attribute MyAttribute on the properties, and initialize the specific instance of JsonSerializer with a hook that knows to look for that attribute.
At first glance, I don't see any of the available hook points in JSON.NET will provide the PropertyInfo for the current property to do such an inspection - only the property's value. Am I missing something? Or a better way to approach this?

Here's a generic reusable "ignore property" resolver based on the accepted answer:
/// <summary>
/// Special JsonConvert resolver that allows you to ignore properties. See https://stackoverflow.com/a/13588192/1037948
/// </summary>
public class IgnorableSerializerContractResolver : DefaultContractResolver {
protected readonly Dictionary<Type, HashSet<string>> Ignores;
public IgnorableSerializerContractResolver() {
this.Ignores = new Dictionary<Type, HashSet<string>>();
}
/// <summary>
/// Explicitly ignore the given property(s) for the given type
/// </summary>
/// <param name="type"></param>
/// <param name="propertyName">one or more properties to ignore. Leave empty to ignore the type entirely.</param>
public void Ignore(Type type, params string[] propertyName) {
// start bucket if DNE
if (!this.Ignores.ContainsKey(type)) this.Ignores[type] = new HashSet<string>();
foreach (var prop in propertyName) {
this.Ignores[type].Add(prop);
}
}
/// <summary>
/// Is the given property for the given type ignored?
/// </summary>
/// <param name="type"></param>
/// <param name="propertyName"></param>
/// <returns></returns>
public bool IsIgnored(Type type, string propertyName) {
if (!this.Ignores.ContainsKey(type)) return false;
// if no properties provided, ignore the type entirely
if (this.Ignores[type].Count == 0) return true;
return this.Ignores[type].Contains(propertyName);
}
/// <summary>
/// The decision logic goes here
/// </summary>
/// <param name="member"></param>
/// <param name="memberSerialization"></param>
/// <returns></returns>
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) {
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (this.IsIgnored(property.DeclaringType, property.PropertyName)
// need to check basetype as well for EF -- #per comment by user576838
|| this.IsIgnored(property.DeclaringType.BaseType, property.PropertyName)) {
property.ShouldSerialize = instance => { return false; };
}
return property;
}
}
And usage:
var jsonResolver = new IgnorableSerializerContractResolver();
// ignore single property
jsonResolver.Ignore(typeof(Company), "WebSites");
// ignore single datatype
jsonResolver.Ignore(typeof(System.Data.Objects.DataClasses.EntityObject));
var jsonSettings = new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, ContractResolver = jsonResolver };

Use the JsonIgnore attribute.
For example, to exclude Id:
public class Person {
[JsonIgnore]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}

You have a few options. I recommend you read the Json.Net documentation article on the subject before reading below.
The article presents two methods:
Create a method that returns a bool value based on a naming convention that Json.Net will follow to determine whether or not to serialize the property.
Create a custom contract resolver that ignores the property.
Of the two, I favor the latter. Skip attributes altogether -- only use them to ignore properties across all forms of serialization. Instead, create a custom contract resolver that ignores the property in question, and only use the contract resolver when you want to ignore the property, leaving other users of the class free to serialize the property or not at their own whim.
Edit To avoid link rot, I'm posting the code in question from the article
public class ShouldSerializeContractResolver : DefaultContractResolver
{
public new static readonly ShouldSerializeContractResolver Instance =
new ShouldSerializeContractResolver();
protected override JsonProperty CreateProperty( MemberInfo member,
MemberSerialization memberSerialization )
{
JsonProperty property = base.CreateProperty( member, memberSerialization );
if( property.DeclaringType == typeof(Employee) &&
property.PropertyName == "Manager" )
{
property.ShouldSerialize = instance =>
{
// replace this logic with your own, probably just
// return false;
Employee e = (Employee)instance;
return e.Manager != e;
};
}
return property;
}
}

Here is a method based on drzaus' excellent serializer contract which uses lambda expressions. Simply add it to the same class. After all, who doesn't prefer the compiler to do the checking for them?
public IgnorableSerializerContractResolver Ignore<TModel>(Expression<Func<TModel, object>> selector)
{
MemberExpression body = selector.Body as MemberExpression;
if (body == null)
{
UnaryExpression ubody = (UnaryExpression)selector.Body;
body = ubody.Operand as MemberExpression;
if (body == null)
{
throw new ArgumentException("Could not get property name", "selector");
}
}
string propertyName = body.Member.Name;
this.Ignore(typeof (TModel), propertyName);
return this;
}
You can now ignore properties easily and fluently:
contract.Ignore<Node>(node => node.NextNode)
.Ignore<Node>(node => node.AvailableNodes);

I don't care to set the property names as strings, in case they ever change it would break my other code.
I had several "view modes" on the objects I needed to serialized, so I ended up doing something like this in the contract resolver (view mode provided by constructor argument):
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (viewMode == ViewModeEnum.UnregisteredCustomer && member.GetCustomAttributes(typeof(UnregisteredCustomerAttribute), true).Length == 0)
{
property.ShouldSerialize = instance => { return false; };
}
return property;
}
Where my objects look like this:
public interface IStatement
{
[UnregisteredCustomer]
string PolicyNumber { get; set; }
string PlanCode { get; set; }
PlanStatus PlanStatus { get; set; }
[UnregisteredCustomer]
decimal TotalAmount { get; }
[UnregisteredCustomer]
ICollection<IBalance> Balances { get; }
void SetBalances(IBalance[] balances);
}
The downside to this would be the bit of reflection in the resolver, but I think it's worth it to have more maintainable code.

I had good results with the combination of both drzaus and Steve Rukuts answers. However, I face a problem when I set JsonPropertyAttribute with a different name or caps for the property. For example:
[JsonProperty("username")]
public string Username { get; set; }
Include UnderlyingName into consideration solves the problem:
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (this.IsIgnored(property.DeclaringType, property.PropertyName)
|| this.IsIgnored(property.DeclaringType, property.UnderlyingName)
|| this.IsIgnored(property.DeclaringType.BaseType, property.PropertyName)
|| this.IsIgnored(property.DeclaringType.BaseType, property.UnderlyingName))
{
property.ShouldSerialize = instance => { return false; };
}
return property;
}

If you are willing to use F# (or simply use an API not optimized for C#), the FSharp.JsonSkippable library allows you to control in a simple and strongly typed manner whether to include a given property when serializing (and determine whether a property was included when deserializing), and moreover, to control/determine exclusion separately of nullability. (Full disclosure: I'm the author of the library.)

Related

Serializing Properties With Same Name

I have the following model structure:
public class A
{
[JsonProperty("id")]
public string Id { get { return "A:ID"; } }
[JsonProperty("CompKey")]
public string CompKey { get; set; }
}
public class B : A
{
[JsonProperty("id")]
public string Id { get { return "B:ID"; } }
[JsonProperty("name")]
public string Name { get; set; }
}
I want to serialize an instance of class B but only want the Id property of class A to be visible.
I created a contract resolver to pass to Newtonsoft:
public class DatabaseEntryResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
property.ShouldSerialize = instance => property.DeclaringType == typeof(A);
return property;
}
}
I use this in the following manner:
var someObject = new A();
var json = JsonConvert.SerializeObject(someObject, new JsonSerializerSettings
{
ContractResolver = new DatabaseEntryResolver()
});
However, neither of the ID properties are being created in the JSON structure. Anyone might shed some insight on this issue I am having?
Json.NET will never serialize two properties with the same name. If DefaultContractResolver encounters two identically named properties, it chooses to serialize the most derived property that is not ignored. Notably, this decision is based on static metadata such as the presence of absence of [JsonIgnore] -- and not on runtime state such as the return from ShouldSerialize.
Thus, to make A.Id supersede B.Id when serializing an instance of B, you must override CreateProperty() as follows:
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (member.ReflectedType == typeof(B)) // Use (typeof(A).IsAssignableFrom(member.ReflectedType)) if you want this to apply to all subclasses of A
{
// Make A.Id supersede B.Id
if (member.Name == nameof(A.Id) && member.DeclaringType != typeof(A))
property.Ignored = true;
}
return property;
}
In addition, if you want really ony want the Id property of class A to be visible when serializing an instance of B, you must in addition remove or ignore everything else in CreateProperties():
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var properties = base.CreateProperties(type, memberSerialization);
if (type == typeof(B)) // Use typeof(A).IsAssignableFrom(type)) if you want to only serialize the Id property of A and all its subclasses
{
// Remove everything except Id. You could also Ignore everything except Id.
properties = properties.Where(p => p.UnderlyingName == nameof(A.Id)).ToList();
}
return properties;
}
Notes:
Presumably Newtonsoft does not check ShouldSerialize to determine which Id property to bind to JSON because ShouldSerialize cannot be called during deserialization.
If you really just want to serialize { "id": "A:ID" } for every instance of A or any subclass of A, use typeof(A).IsAssignableFrom() where I mention it in code comments.
You should mark B.Id with the new modifier to avoid a compilation warning.
public class B : A
{
[JsonProperty("id")]
public new string Id { get { return "B:ID"; } } // Fixed compilation
For more see Knowing When to Use Override and New Keywords (C# Programming Guide).
Demo fiddle here;
For whoever has encountered this issue, an explanation was given by the guys who developer Netwonsoft.JSON here, such that my new contract resolver is the following:
public class DatabaseEntryResolver : DefaultContractResolver
{
protected override List<MemberInfo> GetSerializableMembers(Type objectType)
{
return base.GetSerializableMembers(objectType).Where(member => member.DeclaringType == typeof(A);
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
property.ShouldSerialize = instance => property.DeclaringType == typeof(A);
return property;
}
}
Note how I still decided to leave the overloaded CreateProperty method, even though technically it is not needed as the overloaded GetSerializableMembers method already filters out the properties I need. I decided to this just in case but without it the solution still works.

How to customize value setting during deserialization with contract resolver and value provider

I am failing at a task to encrypt certain fields when serializing into JSON and decrypt those fields during deserialization into a specific C# class.
I reduced the problem to its most basic issue, which is that I cannot customize the deserialization of specific fields by manipulating the value, and I don't know the reason. I am using a custom contract resolver and a custom value provider for each field. I can see that the GetValue function is executed but the SetValue is never executed.
The code sample:
class Program
{
static void Main(string[] args)
{
var text = "This is text";
var number = 1;
var anotherText = "This is another text";
var anotherNumber = 2;
var sampleInner = new SampleInner(anotherText, anotherNumber);
var sample = new SampleMessage(text, number, sampleInner);
var myCustomContractResolver = new MyCustomContractResolver();
var jsonSettings = GetJsonSettings(myCustomContractResolver);
Console.WriteLine("Serializing..");
var json = JsonConvert.SerializeObject(sample, jsonSettings);
Console.WriteLine(json);
Console.WriteLine("Deserializing..");
var sampleDeserialized = JsonConvert.DeserializeObject(json, typeof(SampleMessage), jsonSettings);
Console.WriteLine(sampleDeserialized);
Console.ReadLine();
}
private static JsonSerializerSettings GetJsonSettings(IContractResolver contractResolver)
{
var jsonSettings =
new JsonSerializerSettings
{
ContractResolver = contractResolver
};
return jsonSettings;
}
}
the custom contract resolver:
public class MyCustomContractResolver
: DefaultContractResolver
{
public MyCustomContractResolver()
{
NamingStrategy = new CamelCaseNamingStrategy();
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var jsonProperties = base.CreateProperties(type, memberSerialization);
foreach (var jsonProperty in jsonProperties)
{
var propertyInfo = type.GetProperty(jsonProperty.UnderlyingName);
var defaultValueProvider = jsonProperty.ValueProvider;
jsonProperty.ValueProvider = new MyValueProvider(defaultValueProvider);
}
return jsonProperties;
}
}
and the custom value provider whose SetValue is never executed during deserialization:
public class MyValueProvider
: IValueProvider
{
private readonly IValueProvider _valueProvider;
public MyValueProvider(IValueProvider valueProvider)
{
_valueProvider = valueProvider;
}
public void SetValue(object target, object value)
{
//This is not executed during deserialization. Why?
_valueProvider.SetValue(target, value);
Console.WriteLine($"Value set: {value}");
}
public object GetValue(object target)
{
var value = _valueProvider.GetValue(target);
Console.WriteLine($"Value get: {value}");
return value;
}
}
Here is the sample code to reproduce it in case it's needed.
Hopefully somebody can let me know what am I missing :)
UPDATE 1: The object I serialize/deserialize is immutable (no public setters) and that's a requirement because I need to support such objects. As a comment points out, then it makes sense there is no SetValue executed
UPDATE 2: Thanks to #dbc for the brilliant answer no I know a good workaround for deserialization into immutable object. The final version code after the accepted answer.
UPDATE 3: The chosen answer is absolutely correct given the question. However, after investigating further I decided to go with a slightly different approach that works for both immutable and mutable classes in case somebody is in a similar situation. Instead using value providers I use now a combination of contract resolver and json converter so that with the contract resolver I can decide how to serialize/deserialize based on the type, and with the json converter I can access the value during serialization/deserialization and manipulate as desired.
Basically, on my contract resolver I override the method to create properties (where I can access my original Type properties) and selectively I specify which json converter to use.
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var jsonProperties = base.CreateProperties(type, memberSerialization);
//Filter here based on type, attribute or whatever and if want to customize a specific property type:
foreach (var jsonProperty in jsonProperties)
{
jsonProperty.Converter = new MyJsonConverter();
}
return jsonProperties;
}
and in the MyJsonConverter we can choose what to do when writing into json or when reading from json:
public class MyJsonConverter
: JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
//I can do whatever with value
var finalValue = $"{value}-edited";
writer.WriteValue(finalValue);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// I can do whatever with the value
var value = (string)reader.Value;
var newValue = "whatever";
return newValue;
}
public override bool CanWrite => true;
public override bool CanRead => true;
public override bool CanConvert(Type objectType)
{
return true;
}
}
The reason that IValueProvider.SetValue is not called for the properties of SampleInner is that SampleInner is immutable and so there are no set methods to be called. Instead, the JSON properties are matched to arguments of the type's single parameterized constructor by name (modulo case), deserialized to the type of the matched argument, and then passed into the constructor as explained here.
Even if you were to make the properties mutable the setter will not be called for properties already passed into the constructor, as Json.NET makes the (reasonable) assumption that passing a property value into the constructor is sufficient to set the property's value.
So, what are your options?
Firstly, you could make your types mutable with a default constructor. The setters and constructor could be private as long as they are marked with appropriate attributes:
public class SampleInner
{
[JsonProperty] // Adding this attribute informs Json.NET that the private setter can be called.
public string AnotherText { get; private set; }
[JsonProperty]
public int AnotherNumber { get; private set; }
[JsonConstructor] // Adding this attribute informs Json.NET that this private constructor can be called
private SampleInner() { }
public SampleInner(string anotherText, int anotherNumber)
{
this.AnotherText = anotherText;
this.AnotherNumber = anotherNumber;
}
}
Now that there are setters to be called, your MyValueProvider.SetValue() will get invoked. Demo fiddle #1 here.
Secondly, if you cannot so modify your types, you could wrap the constructor method called in some decorator that does the necessary pre-processing, however this is made difficult by the fact that JsonObjectContract.ParameterizedCreator is nonpublic. Thus you cannot get direct access to the parameterized constructor that Json.NET has chosen, in order to decorate it. You can, however, determine its arguments, which are specified by JsonObjectContract.CreatorParameters. When this collection is populated then either OverrideCreator is set or the (secret) ParameterizedCreator is set. This allows the necessary logic to be inserted as follows:
public class MyCustomContractResolver : DefaultContractResolver
{
public MyCustomContractResolver() { NamingStrategy = new CamelCaseNamingStrategy(); }
static ObjectConstructor<Object> GetParameterizedConstructor(JsonObjectContract contract)
{
if (contract.OverrideCreator != null)
return contract.OverrideCreator;
// Here we assume that JsonSerializerSettings.ConstructorHandling == ConstructorHandling.Default
// If you would prefer AllowNonPublicDefaultConstructor then you need to remove the check on contract.DefaultCreatorNonPublic
if (contract.CreatorParameters.Count > 0 && (contract.DefaultCreator == null || contract.DefaultCreatorNonPublic))
{
// OK, Json.NET has a parameterized constructor stashed away in JsonObjectContract.ParameterizedCreator
// https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs#L100
// But, annoyingly, this value is internal so we cannot get it!
// But because CreatorParameters.Count > 0 and OverrideCreator == null we can infer that such a constructor exists, and so call it using Activator.CreateInstance
return (args) => Activator.CreateInstance(contract.CreatedType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, args, CultureInfo.InvariantCulture);
}
return null;
}
static ObjectConstructor<Object> CustomizeConstructor(JsonObjectContract contract, ObjectConstructor<Object> constructor)
{
if (constructor == null)
return null;
return (args) =>
{
// Add here your customization logic.
// You can match creator parameters to properties by property name if needed.
foreach (var pair in args.Zip(contract.CreatorParameters, (a, p) => new { Value = a, Parameter = p }))
{
// Get the corresponding property in case you need to, e.g., check its attributes:
var property = contract.Properties[pair.Parameter.PropertyName];
if (property == null)
Console.WriteLine("Argument {0}: Value {1}", pair.Parameter.PropertyName, pair.Value);
else
Console.WriteLine("Argument {0} (corresponding to JsonProperty {1}): Value {2}", pair.Parameter.PropertyName, property, pair.Value);
}
return constructor(args);
};
}
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
contract.OverrideCreator = CustomizeConstructor(contract, GetParameterizedConstructor(contract));
return contract;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var jsonProperties = base.CreateProperties(type, memberSerialization);
foreach (var jsonProperty in jsonProperties)
{
var defaultValueProvider = jsonProperty.ValueProvider;
jsonProperty.ValueProvider = new MyValueProvider(defaultValueProvider);
}
return jsonProperties;
}
}
Notes:
If a default constructor exists but is nonpublic, the above contract resolver assumes that it is not used. If you would prefer nonpublic default constructors to be used, you will need to set JsonSerializerSettings.ConstructorHandling == ConstructorHandling.AllowNonPublicDefaultConstructor and also modify the code in GetParameterizedConstructor() above to remove the check on contract.DefaultCreatorNonPublic:
if (contract.CreatorParameters.Count > 0 && contract.DefaultCreator == null)
It would be reasonable to request an enhancement to allow access to, and customization of, JsonObjectContract.ParameterizedCreator.
(I suppose you could try to access JsonObjectContract.ParameterizedCreator directly via reflection...)
Demo fiddle #2 here.

NewtonSoft JsonConverter - Access other properties

I have a need to format the output json of a decimal to a currency, with the culture specified my the object I am serializing, the object could be nested so I cannot preset the option in the serializer. The current way I am doing this is by using extra string properties that format the output.
[JsonIgnore]
public decimal Cost {get;set;}
[JsonIgnore]
public CultureInfo Culture {get;set;}
public string AsCurrency(decimal value) {
return string.Format(this.Culture, "{0:c}", value);
}
[JsonProperty("FormattedCost")]
public string FormatedCost {
get { return this.AsCurrency(this.Cost); }
}
I have alot of properties to deal with, I'm not bothered about Deserializing, the JsonObject is used by a different language to populated a PDF and so I want the string values.
Ideally I'd like a JsonConverter so I can just do
[JsonProperty("FormattedCost")]
[JsonConverter(typeof(MyCurrencyConverter))]
public decimal Cost {get;set;}
The issue I have is how to access the Culture property of the containing object in the converter.
public class MyCurrencyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var culture = // How do I get the Culture from the parent object?
writer.WriteValue(string.format(culture, "{0:c}", (decimal)value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return typeof(decimal) == objectType;
}
}
As Requested sample JSON.
for an array of Contract classes that each have a Cost and an Culture.
[{ FormattedCost : "£5000.00"}, { FormattedCost : "$8000.00"}, { FormattedCost : "€599.00"}]
The actual objects are a lot more complicated, multiple fields with nested Assets that would have their own figures. Additionally not all decimals would be currencies.
I don't really want to have to write a custom serializer for the Contract itself as I would then have to modify it each time the properties change.
The ideal solution is being able to tag certain decimal properties with the converter attribute so it can handle it.
The other way I was thinking of going was to make a custom class for the decimal properties with an implicit conversion from decimal, however that gets more complicated as some properties are calculated properties based on previous results.
WORKAROUND
I have a work-around for my use case, but it uses reflection to obtain a private variable in the serializer.
var binding = BindingFlags.NonPublic | BindingFlags.Instance;
var writer = serializer.GetType()
.GetMethod("GetInternalSerializer", binding)
?.Invoke(serializer, null);
var parent = writer?.GetType()
.GetField("_serializeStack", binding)
?.GetValue(writer) is List<object> stack
&& stack.Count > 1 ? stack[stack.Count - 2] as MyType: null;
In my tested use cases this gives me the parent object, but it's not using the public API.
What you want to do is to intercept and modify the value of a specific property of an object as it is being serialized while using default serialization for all other properties. This can be done with a custom ContractResolver that replaces the ValueProvider of the property in question when a specific attribute is applied.
First, define the following attribute and contract resolver:
[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field, AllowMultiple = false)]
public class JsonFormatAttribute : System.Attribute
{
public JsonFormatAttribute(string formattingString)
{
this.FormattingString = formattingString;
}
/// <summary>
/// The format string to pass to string.Format()
/// </summary>
public string FormattingString { get; set; }
/// <summary>
/// The name of the underlying property that returns the object's culture, or NULL if not applicable.
/// </summary>
public string CulturePropertyName { get; set; }
}
public class FormattedPropertyContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
return base.CreateProperties(type, memberSerialization)
.AddFormatting();
}
}
public static class JsonContractExtensions
{
class FormattedValueProvider : IValueProvider
{
readonly IValueProvider baseProvider;
readonly string formatString;
readonly IValueProvider cultureValueProvider;
public FormattedValueProvider(IValueProvider baseProvider, string formatString, IValueProvider cultureValueProvider)
{
this.baseProvider = baseProvider;
this.formatString = formatString;
this.cultureValueProvider = cultureValueProvider;
}
#region IValueProvider Members
public object GetValue(object target)
{
var value = baseProvider.GetValue(target);
var culture = cultureValueProvider == null ? null : (CultureInfo)cultureValueProvider.GetValue(target);
return string.Format(culture ?? CultureInfo.InvariantCulture, formatString, value);
}
public void SetValue(object target, object value)
{
// This contract resolver should only be used for serialization, not deserialization, so throw an exception.
throw new NotImplementedException();
}
#endregion
}
public static IList<JsonProperty> AddFormatting(this IList<JsonProperty> properties)
{
ILookup<string, JsonProperty> lookup = null;
foreach (var jsonProperty in properties)
{
var attr = (JsonFormatAttribute)jsonProperty.AttributeProvider.GetAttributes(typeof(JsonFormatAttribute), false).SingleOrDefault();
if (attr != null)
{
IValueProvider cultureValueProvider = null;
if (attr.CulturePropertyName != null)
{
if (lookup == null)
lookup = properties.ToLookup(p => p.UnderlyingName);
var cultureProperty = lookup[attr.CulturePropertyName].FirstOrDefault();
if (cultureProperty != null)
cultureValueProvider = cultureProperty.ValueProvider;
}
jsonProperty.ValueProvider = new FormattedValueProvider(jsonProperty.ValueProvider, attr.FormattingString, cultureValueProvider);
jsonProperty.PropertyType = typeof(string);
}
}
return properties;
}
}
Next, define your object as follows:
public class RootObject
{
[JsonFormat("{0:c}", CulturePropertyName = nameof(Culture))]
public decimal Cost { get; set; }
[JsonIgnore]
public CultureInfo Culture { get; set; }
public string SomeValue { get; set; }
public string SomeOtherValue { get; set; }
}
Finally, serialize as follows:
var settings = new JsonSerializerSettings
{
ContractResolver = new FormattedPropertyContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy(),
},
};
var json = JsonConvert.SerializeObject(root, Formatting.Indented, settings);
Notes:
Since you are not serializing the culture name, I can't see any way to deserialize the Cost property. Thus I threw an exception from the SetValue method.
(And, even if you were serializing the culture name, since a JSON object is an unordered set of name/value pairs according the standard, there's no way to guarantee the culture name appears before the cost in the JSON being deserialized. This may be related to why Newtonsoft does not provide access to the parent stack. During deserialization there's no guarantee that required properties in the parent hierarchy have been read - or even that the parents have been constructed.)
If you have to apply several different customization rules to your contracts, consider using ConfigurableContractResolver from How to add metadata to describe which properties are dates in JSON.Net.
You may want to cache the contract resolver for best performance.
Another approach would be to add a converter to the parent object that generates a default serialization to JObject by disabling itself temporarily, tweaks the returned JObject, then writes that out. For examples of this approach see JSON.Net throws StackOverflowException when using [JsonConvert()] or Can I serialize nested properties to my class in one operation with Json.net?.
In comments you write, Inside WriteJson I cannot figure out how to access the parent object and it's properties. It should be possible to do this with a custom IValueProvider that returns a Tuple or similar class containing the parent and the value, which would be used in concert with a specific JsonConverter that expects such input. Not sure I'd recommend this though since it's extremely tricky.
Working sample .Net fiddle.

Ignore Base Class Properties in Json.NET Serialization

I have the following class structure:
[JsonObject]
public class Polygon : IEnumerable<Point>
{
public List<Point> Vertices { get; set; }
public AxisAlignedRectangle Envelope { get; set; }
}
public class AxisAlignedRectangle : Polygon {
public double Left { get; set; }
...
}
I am serializing the Polygon class, but when I do, I get a JsonSerializationException, with the message
Self referencing loop detected for property 'Envelope' with type 'MyNamespace.AxisAlignedRectangle'.
If I add [JsonObject(IsReference = true)] (as described here) to AxisAlignedRectangle, the code runs fine, but I get an auto-assigned $id field in each instance of AxisAlignedRectangle, and a $ref field when that instance is re-referenced. For example, when I serialize a polygon, I get:
{
Vertices: [ ... ],
Envelope: {
$id: '1',
Left: -5,
...
Vertices: [ ... ],
Envelope: {
$ref: '1'
}
}
}
My desire is to remove the Polygon properties entirely when I serialize an AxisAlignedRectangle. I tried adding a DataContractAttribute to the AxisAlignedRectangle class (along with appropriate DataMemberAttribute attributes), but all the properties of Polygon were still being serialized. This was unexpected, since there is an example in the Json.NET documentation that appears to indicate such an approach should work.
Does anyone know a way to explicitly remove (most importantly) the Envelope property from the resulting Json.NET serialization, when the type being serialized is AxisAlignedRectangle? Thanks.
Most simple way to do it is simply decorate the AxisAlignedRectangle object with [JsonObject(MemberSerialization.OptIn)].
In a sentence, it will serialize only properties decorated with [JsonProperty] attribute.
You can read more here: MemberSerialization Enumeration.
Another option is to decorate the Polygon properties with JsonIgnoreAttribute Class.
I've run into the same thing. The JsonIgnoreAttribute is a good solution if a certain property should always be ingored and you have access to the class containing the property. But if you want to determine which properties should be serialized at serialization-time, you can use a ContractResolver.
Here's an implementation that allows you to serialize properties starting with the most derived class and stopping at a given base class. In my case, I wanted to serialize the properties of my custom CMS (EPiServer) page types, but didn't want to serialize all of the built-in properties of the page classes.
public class DerivedClassContractResolver : DefaultContractResolver
{
private Type _stopAtBaseType;
public DerivedClassContractResolver(Type stopAtBaseType)
{
_stopAtBaseType = stopAtBaseType;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
Type originalType = GetOriginalType(type);
IList<JsonProperty> defaultProperties = base.CreateProperties(type, memberSerialization);
List<string> includedProperties = Utilities.GetPropertyNames(originalType, _stopAtBaseType);
return defaultProperties.Where(p => includedProperties.Contains(p.PropertyName)).ToList();
}
private Type GetOriginalType(Type type)
{
Type originalType = type;
//If the type is a dynamic proxy, get the base type
if (typeof(Castle.DynamicProxy.IProxyTargetAccessor).IsAssignableFrom(type))
originalType = type.BaseType ?? type;
return originalType;
}
}
public class Utilities
{
/// <summary>
/// Gets a list of all public instance properties of a given class type
/// excluding those belonging to or inherited by the given base type.
/// </summary>
/// <param name="type">The Type to get property names for</param>
/// <param name="stopAtType">A base type inherited by type whose properties should not be included.</param>
/// <returns></returns>
public static List<string> GetPropertyNames(Type type, Type stopAtBaseType)
{
List<string> propertyNames = new List<string>();
if (type == null || type == stopAtBaseType) return propertyNames;
Type currentType = type;
do
{
PropertyInfo[] properties = currentType.GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance);
foreach (PropertyInfo property in properties)
if (!propertyNames.Contains(property.Name))
propertyNames.Add(property.Name);
currentType = currentType.BaseType;
} while (currentType != null && currentType != stopAtBaseType);
return propertyNames;
}
}
This let's me do something like this:
JsonConvert.SerializeObject(page, new JsonSerializerSettings()
{
ContractResolver = new DerivedClassContractResolver(typeof(EPiServer.Core.PageData))
}));
to get the properties I have defined on my own class(es) without getting the slew of properties inherited from EPiServer.Core.PageData. Note: You don't need the GetOriginalType() code if you're not using the Castle DynamicProxy project (which the EPiServer CMS does.)
You can use conditional property serialization, by defining your classes like this:
[JsonObject]
public class Polygon : IEnumerable<Point>
{
public List<Point> Vertices { get; set; }
public AxisAlignedRectangle Envelope { get; set; }
public virtual bool ShouldSerializeEnvelope()
{
return true;
}
}
public class AxisAlignedRectangle : Polygon
{
public double Left { get; set; }
...
public override bool ShouldSerializeEnvelope()
{
return false;
}
}
I have posted the full solution at:
https://github.com/thiagoavelino/VisualStudio_C/blob/master/VisualStudio_C/StackOverFlow/ParsingJason/EnvelopePolygonProblem.cs
In WPF its common to want to ignore Observables
public class TypeOnlyContractResolver<T> : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
property.ShouldSerialize = instance =>
{
return !property.DeclaringType.AssemblyQualifiedName.Contains("Observable");
};
property.ShouldDeserialize = instance =>
{
return !property.DeclaringType.AssemblyQualifiedName.Contains("Observable");
};
return property;
}
}
This will ignore anything with the base class ObservableObject etc.

How to use JSON.Net in a refactoring safe way for persistent data

I'm using JSON.Net to save collections of objects inheriting from an interface. I've found I can set TypeNameHandling as in
JsonSerializerSettings.TypeNameHandling = TypeNameHandling.Auto;
and I can get the fully qualified type name of the objects injected into the emitted json which can be used on serialization to construct the correct object.
"$type": "Newtonsoft.Json.Samples.Hotel, Newtonsoft.Json.Tests"
However I'm pretty trigger happy with my resharper refactoring and this kind of mapping will break quickly between versions of the code.
What I'd like to to is instead of storing the fully qualified typename I would assign each class a Guid as a type id which will never change under refactoring and use that to map back to the deserialization code.
Is this something someone has tried before and if so how to go about doing this?
EDIT
We now have an open source project to help with migrations.
https://github.com/Weingartner/Migrations.Json.Net
ORIGINAL ANSWER
Here is the JsonConverter I wrote to solve my problem. What I did was use the Guid attribute ( which is normally used for com interop ) to tag my serialization. I then provided a factory based on the guid. The factory was based on the ninject DI framework.
The critical line of code is in the ReadJson method
var document = _Kernel.Get<IWeinCadDocument>(typeId.ToString());
here I create an instance of my document of the correct type based on the Guid string. The types are pre-registered using the following function.
public void BindDocumentTypes(params Type[] types)
{
Debug.Assert
(types.All(p => typeof (IWeinCadDocument).IsAssignableFrom(p)));
foreach (var documentType in types)
{
Kernel.Bind<IWeinCadDocument>()
.To(documentType)
.Named(documentType.GUID.ToString());
}
}
which can be used in the following way to register a set of types with their Guid.
BindDocumentTypes(
typeof (ADocument), typeof (BDocument), typeof (CDocument)
);
To make sure the Guid does not change between versions of the software or even compiles we enforce the Guid like so with an attribute.
[Guid("4882176A-751A-4153-928A-915BEA87FAB3")]
public class ADocument : WeinCadDocumentBase<ADocument>
{
public ADocument( IWeinCadDocumentStorage storage )
: base(storage)
{
}
public override object PersistentData
{
get
{
return new DocumentData(10, 20);
}
}
}
the full JSonConverter is below.
public class WeinCadDocumentConverter : JsonConverter
{
private readonly IKernel _Kernel;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var document = value as IWeinCadDocument;
Debug.Assert(document != null, "document != null");
AssertThatWeHaveACustomGuidSet(value);
writer.WriteStartObject();
writer.WritePropertyName("InstanceId");
writer.WriteValue(document.InstanceId);
writer.WritePropertyName("TypeId");
writer.WriteValue(document.GetType().GUID);
writer.WritePropertyName("Name");
writer.WriteValue(document.Name);
writer.WritePropertyName("Data");
serializer.Serialize(writer, document.PersistentData);
writer.WriteEndObject();
}
/// <summary>
/// The object need a custom GuidAttribute to be set on the class otherwise the
/// GUID may change between versions of the code or even runs of the applications.
/// This Guid is used for identifying types from the document store and calling
/// the correct factory.
/// </summary>
/// <param name="value"></param>
private static void AssertThatWeHaveACustomGuidSet(object value)
{
var attr = System.Attribute.GetCustomAttributes(value.GetType())
.Where(a => a is GuidAttribute)
.ToList();
if (attr.Count == 0)
throw new ArgumentException
(String.Format
(#"Type '{0}' does not have a custom GuidAttribute set. Refusing to serialize.",
value.GetType().Name),
"value");
}
public override object ReadJson
(JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
JObject json = JObject.Load(reader);
var props = json.Properties().ToList();
var instanceId = (Guid) props[0].Value;
var typeId = (Guid) props[1].Value;
var name = (string) props[2].Value;
var data = props[3].Value;
var document = _Kernel.Get<IWeinCadDocument>(typeId.ToString());
document.PersistentData = data;
document.InstanceId = instanceId;
document.Name = name;
return document;
}
public override bool CanConvert(Type objectType)
{
return typeof (IWeinCadDocument).IsAssignableFrom(objectType);
}
public WeinCadDocumentConverter(IKernel kernel)
{
_Kernel = kernel;
}
}

Categories

Resources