Is it possible to have a dynamic IContractResolver? [duplicate] - c#

I'm running into a problem where an ASP.NET MVC html helper method I created is not being "regenerated" each time it is called.
The purpose of the helper method is to create Javascript objects to be used in an angularjs framework. For example, here's a code snippet where the helper method is used (called from within a script tag of an html page):
var app = angular.module( "appName", ["ui.bootstrap"] );
app.controller( 'appCtrl', function( $scope ) {
$scope.model = #Html.ToJavascript( Model, new string[] { "FirstName", "LastName", "ID", "Role" } );
} );
Model is an instance of a class which has a variety of properties, but I only want FirstName, LastName, ID and Role to get serialized to a javascript object.
The ToJavascript() helper method is defined in a statis class as follows:
public static HtmlString ToJavascript( this HtmlHelper helper, object toConvert, string[] includedFields = null, Formatting formatting = Formatting.Indented, ReferenceLoopHandling loopHandling = ReferenceLoopHandling.Ignore )
{
using( var stringWriter = new StringWriter() )
using( var jsonWriter = new JsonTextWriter( stringWriter ) )
{
var serializer = new JsonSerializer()
{
// Let's use camelCasing as is common practice in JavaScript
ContractResolver = new SpecificFieldsResolver( includedFields ),
Formatting = formatting,
ReferenceLoopHandling = loopHandling,
};
// We don't want quotes around object names
jsonWriter.QuoteName = false;
serializer.Serialize( jsonWriter, toConvert );
return new HtmlString( stringWriter.ToString() );
}
}
This utilizes Json.NET to do the actual serialization.
One of the many cool features of Json.NET is that it lets you define, on the fly, which fields get serialized. That's what the SpecificFieldsResolver does. I've defined it as follows:
public class SpecificFieldsResolver : CamelCasePropertyNamesContractResolver
{
private string[] _included;
public SpecificFieldsResolver( string[] included )
{
_included = included;
}
protected override JsonProperty CreateProperty( MemberInfo member, MemberSerialization memberSerialization )
{
JsonProperty prop = base.CreateProperty( member, memberSerialization );
bool inclField = ( _included == null )
|| _included.Contains( member.Name, StringComparer.CurrentCultureIgnoreCase );
prop.ShouldSerialize = obj => inclField;
return prop;
}
}
What's confusing me is the way that CreateProperty() gets called. Specifically, it seems to only get called once for each type of object being serialized.
That's a problem because in another cshtml file I have another call to ToJavascript() which is attempting to serialize the same type of object, but with different fields to be output from the serialization:
var app = angular.module( "app2Name", ["ui.bootstrap"] );
app.controller( 'app2Ctrl', function( $scope ) {
$scope.model = #Html.ToJavascript( Model, new string[] { "FirstName", "LastName", "ID", "Role", "Category", "VoterID" } );
} );
Category and VoterID are also valid class fields. But ToJavascript() doesn't seralize them. Instead, it only serializes the fields defined in the first call to ToJavascript()...even though that call takes place in a different cshtml file. It's as if SpecificFieldsResolver remembers the JsonProperty objects it creates.
Thoughts?
Update
Thanx to dbc for diagnosing exactly what was wrong and suggesting a workaround. I adapated it slightly because I rely on Json.NET's camel case name resolution in several resolvers:
public class CamelCaseNameMapper : CamelCasePropertyNamesContractResolver
{
public string ToCamelCase( string propertyName )
{
return ResolvePropertyName( propertyName );
}
}
public class MaoDefaultContractResolver : DefaultContractResolver
{
private CamelCaseNameMapper _mapper = new CamelCaseNameMapper();
protected override string ResolvePropertyName( string propertyName )
{
return _mapper.ToCamelCase( propertyName );
}
}
Now every resolver, such as my SpecificFieldsResolver, which derives from MaoDefaultContractResolver automatically inherits camel casing but avoids the caching problem the dbc identified.

This appears to be a bug with CamelCasePropertyNamesContractResolver. Its base class, DefaultContractResolver, has two constructors: a parameterless constructor, and a DefaultContractResolver (Boolean) version (just made obsolete in Json.NET 7.0). This parameter has the following meaning:
shareCache
Type: System.Boolean
If set to true the DefaultContractResolverwill use a cached shared with other resolvers of the same type. Sharing the cache will significantly improve performance with multiple resolver instances because expensive reflection will only happen once. This setting can cause unexpected behavior if different instances of the resolver are suppose to produce different results. When set to false it is highly recommended to reuse DefaultContractResolver instances with the JsonSerializer.
The default is false.
Unfortunately, the default constructor for CamelCasePropertyNamesContractResolver sets the value to true:
public class CamelCasePropertyNamesContractResolver : DefaultContractResolver
{
public CamelCasePropertyNamesContractResolver()
#pragma warning disable 612,618
: base(true)
#pragma warning restore 612,618
{
NamingStrategy = new CamelCaseNamingStrategy
{
ProcessDictionaryKeys = true,
OverrideSpecifiedNames = true
};
}
}
Further, there is no second constructor with the shareCache option. This breaks your SpecificFieldsResolver.
As a workaround, you could derive your resolver from DefaultContractResolver and use CamelCaseNamingStrategy to do the name mapping:
public class IndependentCamelCasePropertyNamesContractResolver : DefaultContractResolver
{
public IndependentCamelCasePropertyNamesContractResolver()
: base()
{
NamingStrategy = new CamelCaseNamingStrategy
{
ProcessDictionaryKeys = true,
OverrideSpecifiedNames = true
};
}
}
public class SpecificFieldsResolver : IndependentCamelCasePropertyNamesContractResolver
{
// Remainder unchanged
}
Note that if you are using a version of Json.NET prior to 9.0, CamelCaseNamingStrategy does not exist. Instead a kludge nested CamelCasePropertyNamesContractResolver can be used to map the names:
public class IndependentCamelCasePropertyNamesContractResolver : DefaultContractResolver
{
class CamelCaseNameMapper : CamelCasePropertyNamesContractResolver
{
// Purely to make the protected method public.
public string ToCamelCase(string propertyName)
{
return ResolvePropertyName(propertyName);
}
}
readonly CamelCaseNameMapper nameMapper = new CamelCaseNameMapper();
protected override string ResolvePropertyName(string propertyName)
{
return nameMapper.ToCamelCase(propertyName);
}
}

Related

System.Text.Json and polymorphic code: not working with WebApi controllers

With .NET 7.0 being released, System.Text.Json is supposed to support polymorphic code. Unfortunately, it seems like it can't be used out of the box when you need to return a derived type's instance from a controller's method. For instance, suppose the following model:
public class Base {}
public class Derived1: Base { }
public class Derived2: Base { }
Suppose also that we've got the following dynamic type info resolver:
public class JsonHierarchyTypeInfoResolver : DefaultJsonTypeInfoResolver {
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) {
var jsonTypeInfo = base.GetTypeInfo(type, options);
if( typeof(Base) == jsonTypeInfo.Type ) {
jsonTypeInfo.PolymorphismOptions = new( ) {
TypeDiscriminatorPropertyName = "$type",
IgnoreUnrecognizedTypeDiscriminators = true,
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization,
DerivedTypes = {
new JsonDerivedType(typeof(Derived1), typeof(Derived1).AssemblyQualifiedName!),
new JsonDerivedType(typeof(Derived2), typeof(Derived2).AssemblyQualifiedName!)
}
};
}
return jsonTypeInfo;
}
}
And then, it's used in the app by doing something like this:
builder.Services.AddControllers( )
.AddJsonOptions(options => {
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
options.JsonSerializerOptions.TypeInfoResolver = new JsonHierarchyTypeInfoResolver( );
});
Let's also assume that we've got a simple controller with a simple method that looks like this:
[ApiController]
[Route("[controller]")]
public class DEMOController : ControllerBase {
[HttpGet]
public ActionResult<Base> GetAsync() {
var derived = new Derived1( );
return Ok(derived);
}
}
Whenever the method is called, it will not generate json with the type descriminator as I expected it to. It seems like the problem lies with the SystemTextJsonOutputFormatter when it tries to serialize the object with the following code:
await JsonSerializer.SerializeAsync(responseStream, context.Object, objectType, SerializerOptions, httpContext.RequestAborted);
and here's how objectType is initialized:
// context.ObjectType reflects the declared model type when specified.
// For polymorphic scenarios where the user declares a return type, but returns a derived type,
// we want to serialize all the properties on the derived type. This keeps parity with
// the behavior you get when the user does not declare the return type and with Json.Net at least at the top level.
var objectType = context.Object?.GetType() ?? context.ObjectType ?? typeof(object);
Since the method uses the derived type, the custom info type resolver won't be able to do its magic. Am I missing something? Is this a known issue? Does this mean that I should keep using json.net instead of trying to migrate to System.Text.Json?
The resolved type for GetAsync would be Derived1 which does not have any polymorphism info. One solution is to add polymorphism info for every type in the hierarchy:
public class JsonHierarchyTypeInfoResolver : DefaultJsonTypeInfoResolver
{
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
var jsonTypeInfo = base.GetTypeInfo(type, options);
// if type inherits or is Base
if (jsonTypeInfo.Type.IsAssignableTo(typeof(Base)))
{
jsonTypeInfo.PolymorphismOptions = new()
{
TypeDiscriminatorPropertyName = "$type",
IgnoreUnrecognizedTypeDiscriminators = true,
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization,
};
var derivedTypes = new[]
{
typeof(WeatherForecastController.Derived1),
typeof(WeatherForecastController.Derived2)
}
.Where(t => jsonTypeInfo.Type.IsAssignableTo(t)) // add only appropriate types
.Select(t => new JsonDerivedType(t, t.AssemblyQualifiedName!));
foreach (var derivedType in derivedTypes)
{
jsonTypeInfo.PolymorphismOptions.DerivedTypes.Add(derivedType);
}
}
return jsonTypeInfo;
}
}

System.Text.Json API is there something like IContractResolver

In the new System.Text.Json; namespace is there something like IContractResolver i am trying to migrate my project away from Newtonsoft.
This is one of the classes i am trying to move:
public class SelectiveSerializer : DefaultContractResolver
{
private readonly string[] fields;
public SelectiveSerializer(string fields)
{
var fieldColl = fields.Split(',');
this.fields = fieldColl
.Select(f => f.ToLower().Trim())
.ToArray();
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
property.ShouldSerialize = o => fields.Contains(member.Name.ToLower());
return property;
}
}
The equivalent types in System.Text.Json -- JsonClassInfo and JsonPropertyInfo -- are internal. There is an open enhancement
Equivalent of DefaultContractResolver in System.Text.Json #31257
asking for a public equivalent. – dbc Nov 25 at 19:11
Github issues:
Open up metadata infrastructure of System.Text.Json #34456
Equivalent of DefaultContractResolver.CreateProperties override in System.Text.Json #60518
Equivalent of DefaultContractResolver in System.Text.Json #31257
Please try this:
I wrote this as an extension to System.Text.Json to offer missing features: https://github.com/dahomey-technologies/Dahomey.Json.
You will find support for programmatic object mapping.
Define your own implementation of IObjectMappingConvention:
public class SelectiveSerializer : IObjectMappingConvention
{
private readonly IObjectMappingConvention defaultObjectMappingConvention = new DefaultObjectMappingConvention();
private readonly string[] fields;
public SelectiveSerializer(string fields)
{
var fieldColl = fields.Split(',');
this.fields = fieldColl
.Select(f => f.ToLower().Trim())
.ToArray();
}
public void Apply<T>(JsonSerializerOptions options, ObjectMapping<T> objectMapping) where T : class
{
defaultObjectMappingConvention.Apply<T>(options, objectMapping);
foreach (IMemberMapping memberMapping in objectMapping.MemberMappings)
{
if (memberMapping is MemberMapping<T> member)
{
member.SetShouldSerializeMethod(o => fields.Contains(member.MemberName.ToLower()));
}
}
}
}
Define your class:
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
Setup json extensions by calling on JsonSerializerOptions the extension method SetupExtensions defined in the namespace Dahomey.Json:
JsonSerializerOptions options = new JsonSerializerOptions();
options.SetupExtensions();
Register the new object mapping convention for the class:
options.GetObjectMappingConventionRegistry().RegisterConvention(
typeof(Employee), new SelectiveSerializer("FirstName,Email,Id"));
Then serialize your class with the regular Sytem.Text.Json API:
Employee employee = new Employee
{
Id = 12,
FirstName = "John",
LastName = "Doe",
Email = "john.doe#acme.com"
};
string json = JsonSerializer.Serialize(employee, options);
// {"Id":12,"FirstName":"John","Email":"john.doe#acme.com"};
Contract customization will be implemented in .NET 7, and is available in Preview 6.
From the documentation page What’s new in System.Text.Json in .NET 7: Contract Customization by Eirik Tsarpalis, Krzysztof Wicher and Layomi Akinrinade:
The contract metadata for a given type T is represented using JsonTypeInfo<T>, which in previous versions served as an opaque token used exclusively in source generator APIs. Starting in .NET 7, most facets of the JsonTypeInfo contract metadata have been exposed and made user-modifiable. Contract customization allows users to write their own JSON contract resolution logic using implementations of the IJsonTypeInfoResolver interface:
public interface IJsonTypeInfoResolver
{
JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options);
}
A contract resolver returns a configured JsonTypeInfo instance for the given Type and JsonSerializerOptions combination. It can return null if the resolver does not support metadata for the specified input type.
Contract resolution performed by the default, reflection-based serializer is now exposed via the DefaultJsonTypeInfoResolver class, which implements IJsonTypeInfoResolver.
Starting from .NET 7 the JsonSerializerContext class used in source generation also implements IJsonTypeInfoResolver.
You can create your own IJsonTypeInfoResolver via one of the following methods:
You can subclass DefaultJsonTypeInfoResolver and override GetTypeInfo(Type, JsonSerializerOptions). This resembles overriding Json.NET's DefaultContractResolver.CreateContract().
You can add an Action<JsonTypeInfo> to DefaultJsonTypeInfoResolver.Modifiers to modify the default JsonTypeInfo generated for selected types after creation.
Combining multiple customizations looks easier with this approach than with the inheritance approach. However, since the modifier actions are applied in order, there is a chance that later modifiers could conflict with earlier modifiers.
You could create your own IJsonTypeInfoResolver from scratch that creates contracts only for those types that interest you, and combine it with some other type info resolver via JsonTypeInfoResolver.Combine(IJsonTypeInfoResolver[]).
JsonTypeInfoResolver.Combine() is also useful when you want to use compile-time generated JsonSerializerContext instances with a runtime contract resolver that customizes serialization for certain types only.
Once you have a custom resolver, you can set it via JsonSerializerOptions.TypeInfoResolver.
Thus your SelectiveSerializer can be converted to a DefaultJsonTypeInfoResolver roughly as follows, using modifiers. First define the following fluent extension methods:
public static partial class JsonSerializerExtensions
{
public static DefaultJsonTypeInfoResolver SerializeSelectedFields(this DefaultJsonTypeInfoResolver resolver, string fields) =>
SerializeSelectedFields(resolver, fields?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? throw new ArgumentNullException(nameof(fields)));
public static DefaultJsonTypeInfoResolver SerializeSelectedFields(this DefaultJsonTypeInfoResolver resolver, IEnumerable<string> membersToSerialize)
{
if (resolver == null)
throw new ArgumentNullException(nameof(resolver));
if (membersToSerialize == null)
throw new ArgumentNullException(nameof(membersToSerialize));
var membersToSerializeSet = membersToSerialize.ToHashSet(StringComparer.OrdinalIgnoreCase); // Possibly this should be changed to StringComparer.Ordinal
resolver.Modifiers.Add(typeInfo =>
{
if (typeInfo.Kind == JsonTypeInfoKind.Object)
{
foreach (var property in typeInfo.Properties)
{
if (property.GetMemberName() is {} name && !membersToSerializeSet.Contains(name))
property.ShouldSerialize = static (obj, value) => false;
}
}
});
return resolver;
}
public static string? GetMemberName(this JsonPropertyInfo property) => (property.AttributeProvider as MemberInfo)?.Name;
}
And now you can set up your JsonSerializerOptions e.g. as follows:
var options = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver()
.SerializeSelectedFields("FirstName,Email,Id"),
// Add other options as required
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
};
Notes:
JsonPropertyInfo.ShouldSerialize (also new in .NET 7) can be used for conditional serialization of properties.
When a JsonPropertyInfo was created by the reflection or source-gen resolvers, JsonPropertyInfo.AttributeProvider will be the underlying PropertyInfo or FieldInfo.
For confirmation see this comment by layomia to System.Text.Json: In .NET 7, how can I determine the JsonPropertyInfo created for a specific member, so I can customize the serialization of that member? #77761.
All serialization metadata should be constructed using locale-invariant string logic. In your code you use ToLower() but it would have been better to use ToLowerInvariant(). In my modifier action I use StringComparer.OrdinalIgnoreCase which avoids the need to lowercase the strings.
System.Text.Json is case-sensitive by default so you might want to use case-sensitive property name matching when filtering selected fields.

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.

Selectively escape HTML in strings during deserialization

I'm looking to write a JsonConverter which escapes HTML in strings, unless the [AllowHtml] attribute has been applied;
private class ObjectWithStrings
{
// will be HTML-escaped
public string Name { get; set; }
// won't be escaped
[AllowHtml]
public string Unsafe { get; set; }
}
So I'm trying to write a JsonConverter with a custom ReadJson property;
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var s = (string)reader.Value;
if (s == null)
{
return null;
}
// here I need to get a PropertyInfo so I can call GetCustomAttribute<AllowHtmlAttribute>();
var encoded = System.Web.Security.AntiXss.AntiXssEncoder.HtmlEncode(s, useNamedEntities: true);
return encoded;
}
The gap I've got is that I can't see if Json.Net will let me know the property I'm reading into. Consequently, I can't figure out how to get the property's custom attributes.
Is there a way to find out what property I'm serialising into, or a different pattern recommended for this kind of thing?
EDIT: I failed to write a clear question; I've attempted to write a JsonConverter which deserialises strings, -- see the implementation above of CanConvert(). I suspect that choice is the start of my problem; I may need to deserialise objects with string properties, and do a standard deserialize except when deserialising particular properties.
From within a custom JsonConverter, you can find the name of the JSON property being deserialized by picking it out of the Path property from the JsonReader.
string propertyName = reader.Path.Split('.').Last();
However, this will not solve your overall problem. Assuming the name of the JSON property matches your target class property, you'd still need a way to get the parent object type so you can get the custom attributes from it. Unfortunately, this information is not available to you inside a converter. A converter is intended to be responsible only for the object type it says it can convert (string in your case), and that object's child properties (none in this case, since string is a primitive). So, to make it work, the converter would need to be written to operate on the parent class, and would then need to handle all the string properties of that class. Since your goal seems to be to apply the HTML encoding behavior to all strings in all classes, then you would need a generic converter that handles all non-primitive types, which could get pretty messy, depending on the breadth of what you're trying to deserialize.
Fortunately, there is a better way. Instead of using a JsonConverter, you can use a custom IContractResolver in combination with a IValueProvider to solve this. A ContractResolver is much better suited to problems like this where you want to apply a certain behavior broadly.
Below is an example of the code you would need. The CustomResolver class extends the DefaultContractResolver provided by Json.Net. The CreateProperties() method inspects the JsonProperty objects created by the base resolver and attaches an instance of the inner HtmlEncodingValueProvider class to any string properties which do not have the [AllowHtml] attribute applied. Each value provider later handles the actual encoding of its target string property via the SetValue() method.
public class CustomResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
// Find all string properties that do not have an [AllowHtml] attribute applied
// and attach an HtmlEncodingValueProvider instance to them
foreach (JsonProperty prop in props.Where(p => p.PropertyType == typeof(string)))
{
PropertyInfo pi = type.GetProperty(prop.UnderlyingName);
if (pi != null && pi.GetCustomAttribute(typeof(AllowHtmlAttribute), true) == null)
{
prop.ValueProvider = new HtmlEncodingValueProvider(pi);
}
}
return props;
}
protected class HtmlEncodingValueProvider : IValueProvider
{
PropertyInfo targetProperty;
public HtmlEncodingValueProvider(PropertyInfo targetProperty)
{
this.targetProperty = targetProperty;
}
// SetValue gets called by Json.Net during deserialization.
// The value parameter has the original value read from the JSON;
// target is the object on which to set the value.
public void SetValue(object target, object value)
{
var encoded = System.Web.Security.AntiXss.AntiXssEncoder.HtmlEncode((string)value, useNamedEntities: true);
targetProperty.SetValue(target, encoded);
}
// GetValue is called by Json.Net during serialization.
// The target parameter has the object from which to read the string;
// the return value is the string that gets written to the JSON
public object GetValue(object target)
{
// if you need special handling for serialization, add it here
return targetProperty.GetValue(target);
}
}
}
To use the resolver, create a new JsonSerializerSettings instance, then set its ContractResolver property to a new instance of the custom resolver and pass the settings to the JsonConvert.DeserializeObject() method.
Here is a short demo:
class Program
{
static void Main(string[] args)
{
string json = #"
{
""Name"" : ""<b>Foo Bar</b>"",
""Description"" : ""<p>Bada Boom Bada Bing</p>"",
}";
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new CustomResolver()
};
Foo foo = JsonConvert.DeserializeObject<Foo>(json, settings);
Console.WriteLine("Name: " + foo.Name);
Console.WriteLine("Desc: " + foo.Description);
}
}
class Foo
{
public string Name { get; set; }
[AllowHtml]
public string Description { get; set; }
}
class AllowHtmlAttribute : Attribute { }
Here is the output. Notice that the Name property gets HTML encoded while the Description property does not.
Name: <b>Foo Bar</b>
Desc: <p>Bada Boom Bada Bing</p>
Fiddle: https://dotnetfiddle.net/cAg4NC

Keep casing when serializing dictionaries

I have a Web Api project being configured like this:
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
However, I want dictionary keys casing to remain unchanged. is there any attribute in Newtonsoft.Json I can use to a class to denote that I want casing to remain unchanged during serialization?
public class SomeViewModel
{
public Dictionary<string, string> Data { get; set; }
}
There is not an attribute to do this, but you can do it by customizing the resolver.
I see that you are already using a CamelCasePropertyNamesContractResolver. If you derive a new resolver class from that and override the CreateDictionaryContract() method, you can provide a substitute DictionaryKeyResolver function that does not change the key names.
Here is the code you would need:
class CamelCaseExceptDictionaryKeysResolver : CamelCasePropertyNamesContractResolver
{
protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
{
JsonDictionaryContract contract = base.CreateDictionaryContract(objectType);
contract.DictionaryKeyResolver = propertyName => propertyName;
return contract;
}
}
Demo:
class Program
{
static void Main(string[] args)
{
Foo foo = new Foo
{
AnIntegerProperty = 42,
HTMLString = "<html></html>",
Dictionary = new Dictionary<string, string>
{
{ "WHIZbang", "1" },
{ "FOO", "2" },
{ "Bar", "3" },
}
};
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new CamelCaseExceptDictionaryKeysResolver(),
Formatting = Formatting.Indented
};
string json = JsonConvert.SerializeObject(foo, settings);
Console.WriteLine(json);
}
}
class Foo
{
public int AnIntegerProperty { get; set; }
public string HTMLString { get; set; }
public Dictionary<string, string> Dictionary { get; set; }
}
Here is the output from the above. Notice that all of the class property names are camel-cased, but the dictionary keys have retained their original casing.
{
"anIntegerProperty": 42,
"htmlString": "<html></html>",
"dictionary": {
"WHIZbang": "1",
"FOO": "2",
"Bar": "3"
}
}
Json.NET 9.0.1 introduced the NamingStrategy class hierarchy to handle this sort of issue. It extracts the logic for algorithmic remapping of property names from the contract resolver to a separate, lightweight class that allows for control of whether dictionary keys, explicitly specified property names, and extension data names (in 10.0.1) are remapped.
By using DefaultContractResolver and setting NamingStrategy to an instance of CamelCaseNamingStrategy you can generate JSON with camel-cased property names and unmodified dictionary keys by setting it in JsonSerializerSettings.ContractResolver:
var resolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy
{
ProcessDictionaryKeys = false,
OverrideSpecifiedNames = true
}
};
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = resolver;
Notes:
The current implementation of CamelCasePropertyNamesContractResolver also specifies that .Net members with explicitly specified property names (e.g. ones where JsonPropertyAttribute.PropertyName has been set) should have their names remapped:
public CamelCasePropertyNamesContractResolver()
{
NamingStrategy = new CamelCaseNamingStrategy
{
ProcessDictionaryKeys = true,
OverrideSpecifiedNames = true
};
}
The above resolver preserves this behavior. If you don't want this, set OverrideSpecifiedNames = false.
Json.NET has several built-in naming strategies including:
CamelCaseNamingStrategy. A camel case naming strategy that contains the name-remapping logic formerly embedded in CamelCasePropertyNamesContractResolver.
SnakeCaseNamingStrategy. A snake case naming strategy.
DefaultNamingStrategy. The default naming strategy. Property names and dictionary keys are unchanged.
Or, you can create your own by inheriting from the abstract base class NamingStrategy.
While it is also possible to modify the NamingStrategy of an instance of CamelCasePropertyNamesContractResolver, since the latter shares contract information globally across all instances of each type, this can lead to unexpected side-effects if your application tries to use multiple instances of CamelCasePropertyNamesContractResolver. No such problem exists with DefaultContractResolver, so it is safer to use when any customization of casing logic is required.
That is a very nice answer. But why not just override the ResolveDictionaryKey?
class CamelCaseExceptDictionaryResolver : CamelCasePropertyNamesContractResolver
{
#region Overrides of DefaultContractResolver
protected override string ResolveDictionaryKey(string dictionaryKey)
{
return dictionaryKey;
}
#endregion
}
The selected answer is perfect but I guess by the time I'm typing this, the contract resolver must change to something like this because DictionaryKeyResolver doesn't exists anymore :)
public class CamelCaseExceptDictionaryKeysResolver : CamelCasePropertyNamesContractResolver
{
protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
{
JsonDictionaryContract contract = base.CreateDictionaryContract(objectType);
contract.PropertyNameResolver = propertyName => propertyName;
return contract;
}
}

Categories

Resources