Ignore ExpandoObject Properties with JSON.Net Serializer Settings - json.net

Is it possible to ignore ExpandoObject properties, in particular those of Delegate type, when using JsonConvert(expando, formatting, serializerSettings)?
Essentially I'm trying to ignore all parsing of the func property in this example expando object:
//{
// func: () => {}
//}
Action func = () => {};
dynamic expando = new ExpandoObject();
expando.func = func;
// should be empty object {}
string json = JsonConvert(expando, formatting, serializerSettings);
The first thing I tried was overriding the converter. Unfortunately this doesn't work, as I see CanConvert called recursively for Action -> DelegateEntry -> some generic type -> RuntimeMethodInfo.
private class ExpandoObjectIgnoreConverter : ExpandoObjectConverter
{
public override bool CanConvert(Type objectType)
{
if (typeof(Delegate).IsAssignableFrom(objectType))
{
return false;
}
return base.CanConvert(objectType);
}
}
A method that works is using an error handler in serialization settings and a contract resolver. When I throw the error, all further processing of the property is ignored, i.e. Action -> DelegateEntry -> some generic type -> RuntimeMethodInfo. However, I'd like to do this more elegantly than throwing an exception if possible.
Error handler:
serializationSettings.Error = (sender, args) =>
{
if (args.ErrorContext.Error is InvalidCastException)
{
args.ErrorContext.Handled = true;
}
}
Contract resolver:
private class ExpandoObjectContractResolver : DefaultContractResolver
{
public override JsonContract ResolveContract(Type type)
{
if (typeof(Delegate).IsAssignableFrom(type))
{
throw new InvalidCastException();
}
else
{
return base.ResolveContract(type);
}
}
}
I'm using the edge library to script nodejs from within a C# process. I'm trying to remove functions from the returned javascript objects from within C#, as they are assigned a Delegate type that doesn't play nicely with JsonConvert.

ExpandoObjectConverter does not have any custom code to write an ExpandoObject. Instead it overrides JsonConverter.CanWrite to return false thereby allowing the expando to be serialized generically as an IDictionary<string, object>.
Thus you can override CanWrite and WriteJson() yourself to filter undesired key/value pairs before serialization:
public class FilteredExpandoObjectConverter : ExpandoObjectConverter
{
public override bool CanWrite { get { return true; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var expando = (IDictionary<string, object>)value;
var dictionary = expando
.Where(p => !(p.Value is System.Delegate))
.ToDictionary(p => p.Key, p => p.Value);
serializer.Serialize(writer, dictionary);
}
}
Then use the converter in settings as follows:
var formatting = Formatting.Indented;
var serializerSettings = new JsonSerializerSettings
{
Converters = { new FilteredExpandoObjectConverter() },
};
var json = JsonConvert.SerializeObject(expando, formatting, serializerSettings);
Note this will only filter delegates values directly owned by an ExpandoObject. If you have a collection containing some delegates, or delegate-valued members in some POCO, they will not be filtered by this code.
Sample fiddle.

Related

Migrate Newtonsoft JsonConverter to JsonConverter<T>

I have the following interface
public interface IApiResult<TResult> : IApiResult
{
TResult Result { get; set; }
}
with a concrete class like this
public class ApiResult<TResult> : ApiResult, IApiResult<TResult>
{
public ApiResult( TResult result ) : base() {
Result = result;
}
public TResult Result { get; set; }
}
When I used Newtonsoft json library I used a JsonConverter to manage polimorphic serialization and deserialization this way
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer ) {
var obj = JObject.Load( reader );
var apiResult = new ApiResult.ApiResult().As<IApiResult>();
//Check if is an IApiResult<T>
if (obj.TryGetValue( nameof( ApiResult<object>.Result ), StringComparison.InvariantCultureIgnoreCase, out var jResult )) {
//Retrieve the result property type in order to create the proper apiResult object
var prop = objectType.GetProperty( nameof( ApiResult<object>.Result ) );
var value = jResult.ToObject( prop.PropertyType, serializer );
var rType = typeof( ApiResult<> ).MakeGenericType( prop.PropertyType );
apiResult = Activator.CreateInstance( rType ).As<IApiResult>();
prop.SetValue( apiResult, value );
}
//Set the messages
var jMessages = obj.GetValue( nameof( ApiResult.ApiResult.Messages ), StringComparison.InvariantCultureIgnoreCase ).ToObject<JObject[]>();
apiResult.Messages = DeserializeReasons( jMessages );
return apiResult;
}
How can I migrate this code to System.Text.Json?
UPDATE
My biggest problem is with the JObject.TryGetvalue function. This function would have returned a deserialized object that let me understood the type. Since that, I was only using some reflection to understand the type of ApiResult<T>.
With the actual UTF8JsonReader class I am only able to read token by token so I can not replicate the previous behavior.
Your question boils down to, Inside JsonConverter<T>.Read(), how can I scan forward in, or load the contents of, a Utf8JsonReader to determine the polymorphic type of object to deserialize without having to manually deserialize token by token as shown in the documentation example?
As of .NET 6 you have a couple options to accomplish this.
Firstly you can copy the Utf8JsonReader struct and scan forward in the copy until you find the property or properties you want. The original, incoming Utf8JsonReader will be unchanged and still point to the beginning of the incoming JSON value. System.Text.Json will always preload the entire JSON object, array or primitive to be deserialized before calling JsonConverter<T>.Read() so you can be certain the require values are present.
To do this, introduce the following extension methods:
public static partial class JsonExtensions
{
public delegate TValue? DeserializeValue<TValue>(ref Utf8JsonReader reader, JsonSerializerOptions options);
///Scan forward in a copy of the Utf8JsonReader to find a property with the specified name at the current depth, and return its value.
///The Utf8JsonReader is not passed by reference so the state of the caller's reader is unchanged.
///This method should only be called inside JsonConverter<T>.Read(), at which point the entire JSON for the object being read should have been pre-loaded
public static bool TryGetPropertyValue<TValue>(this Utf8JsonReader reader, string name, StringComparison comparison, JsonSerializerOptions options, out TValue? value) =>
reader.TryGetPropertyValue<TValue>(name, comparison, options, (ref Utf8JsonReader r, JsonSerializerOptions o) => JsonSerializer.Deserialize<TValue>(ref r, o), out value);
public static bool TryGetPropertyValue<TValue>(this Utf8JsonReader reader, string name, StringComparison comparison, JsonSerializerOptions options, DeserializeValue<TValue> deserialize, out TValue? value)
{
if (reader.TokenType == JsonTokenType.Null)
goto fail;
else if (reader.TokenType == JsonTokenType.StartObject)
reader.ReadAndAssert();
do
{
if (reader.TokenType != JsonTokenType.PropertyName)
throw new JsonException();
var currentName = reader.GetString();
reader.ReadAndAssert();
if (String.Equals(name, currentName, comparison))
{
value = deserialize(ref reader, options);
return true;
}
else
{
reader.Skip();
}
}
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject);
fail:
value = default;
return false;
}
static void ReadAndAssert(ref this Utf8JsonReader reader)
{
if (!reader.Read())
throw new JsonException();
}
}
public static partial class ObjectExtensions
{
public static T ThrowOnNull<T>(this T? value) where T : class => value ?? throw new ArgumentNullException();
}
And now your Newtonsoft converter might be rewritten to look something like:
public class ApiResultConverter : System.Text.Json.Serialization.JsonConverter<IApiResult>
{
record MessagesDTO(Message [] Messages); // Message is the presumed type the array elements of ApiResult.ApiResult.Messages, which is not shown in your question.
public override IApiResult? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
IApiResult? apiResult = null;
if (reader.TryGetPropertyValue(nameof( ApiResult<object>.Result ), StringComparison.OrdinalIgnoreCase, options,
(ref Utf8JsonReader r, JsonSerializerOptions o) =>
{
var prop = typeToConvert.GetProperty( nameof( ApiResult<object>.Result ) ).ThrowOnNull();
return (Value : JsonSerializer.Deserialize(ref r, prop.PropertyType, o), Property : prop);
},
out var tuple))
{
var rType = typeof( ApiResult<> ).MakeGenericType( tuple.Property.PropertyType );
apiResult = Activator.CreateInstance( rType ).As<IApiResult>().ThrowOnNull();
tuple.Property.SetValue( apiResult, tuple.Value );
}
if (apiResult == null)
apiResult = new ApiResult.ApiResult().As<IApiResult>();
// Now consume the contents of the Utf8JsonReader by deserializing to MessagesDTO.
var dto = JsonSerializer.Deserialize<MessagesDTO>(ref reader, options);
apiResult.Messages = dto?.Messages ?? Array.Empty<Message>();
return apiResult;
}
public override void Write(Utf8JsonWriter writer, IApiResult value, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
Notes:
This approach works well when scanning forward for a single simple property, e.g. a type discriminator string. If the type discriminator string is likely to be at the beginning of the JSON object it will be quite efficient. (This does not seem to apply in your case.)
JsonConverter<T>.Read() must completely consume the incoming token. E.g. if the incoming token is of type JsonTokenType.StartObject then, when exiting, the reader must be positioned on a token of type JsonTokenType.EndObject at the same depth. Thus if you only scan forward in copies of the incoming Utf8JsonWriter you must advance the incoming reader to the end of the current token by calling reader.Skip() before exiting.
Both Json.NET and System.Text.Json use StringComparison.OrdinalIgnoreCase for case-invariant property name matching, so I recommend doing so as well.
Secondly, you can load the contents of your Utf8JsonReader into a JsonDocument or JsonNode, query its properties, then deserialize to your final desired type with one of the JsonSerializer.Deserialzie() overloads that accepts a JSON document or node.
Using this approach with JsonObject in place of JObject, your converter might look something like:
public class ApiResultConverter : System.Text.Json.Serialization.JsonConverter<IApiResult>
{
public override IApiResult? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var obj = JsonNode.Parse(ref reader, new JsonNodeOptions { PropertyNameCaseInsensitive = true })?.AsObject();
if (obj == null)
return null; // Or throw JsonException() if you prefer
IApiResult? apiResult = null;
if (obj.TryGetPropertyValue( nameof( ApiResult<object>.Result ), out var jResult ))
{
//Retrieve the result property type in order to create the proper apiResult object
var prop = typeToConvert.GetProperty( nameof( ApiResult<object>.Result ) ).ThrowOnNull();
var value = JsonSerializer.Deserialize(jResult, prop.PropertyType, options);
var rType = typeof( ApiResult<> ).MakeGenericType( prop.PropertyType );
apiResult = Activator.CreateInstance( rType ).As<IApiResult>();
prop.SetValue( apiResult, value );
}
if (apiResult == null)
apiResult = new ApiResult.ApiResult().As<IApiResult>();
//Set the messages
JsonObject? []? messages = obj[nameof( ApiResult.Messages )]?.AsArray()?.Select(i => i?.AsObject())?.ToArray();
apiResult.Messages = DeserializeReasons(messages); // Not shown in your question
return apiResult;
}
static JsonObject? [] DeserializeReasons(JsonObject? []? messages) => messages == null ? Array.Empty<JsonObject>() : messages;
public override void Write(Utf8JsonWriter writer, IApiResult value, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
public static partial class ObjectExtensions
{
public static T ThrowOnNull<T>(this T? value) where T : class => value ?? throw new ArgumentNullException();
}
Notes:
This approach works well when you have multiple properties (possibly with complex values) to search for and load during the conversion process.
By loading the JsonObject with JsonNodeOptions.PropertyNameCaseInsensitive = true, all property name lookups in the deserialized JsonNode hierarchy will be case-insensitive (using StringComparer.OrdinalIgnoreCase matching as shown in the source).
Since your question does not include a compilable example, the above converters are untested.

Cannot Deserialize Recursively a Class Hierarchy Using JSON.NET

I have a class hierarchy like this:
class Rule { }
class Condition { List<Rule> Rules { get; set; } }
Forget about the remaining properties. I need to deserialize from a JSON string, using a custom JsonConverter. The problem is, I have code for each specific case, but I cannot have it ran recursively, for taking care of the Rules property, each of its elements can be a Condition too.
My code looks like this (ReadJson method):
var jo = JObject.Load(reader);
Rule rule = null;
if (jo["condition"] == null)
{
rule = new Rule();
//fill the properties for rule
}
else
{
rule = new Condition();
//I now want the converter to go through all the values in jo["rules"] and turn them into Rules or Conditions
}
What is the best way to achieve this? I tried to get the JSON for the remaining part, if the object is found to be a Condition:
var json = jo.GetValue("rule").ToString();
But I cannot deserialize it like this, it throws an exception:
var rules = JsonConvert.DeserializeObject<Rule[]>(json, this);
The exception is: JsonReaderException : Error reading JObject from JsonReader. Current JsonReader item is not an object: StartArray. Path '', line 1, position 1.
Any ideas?
You're not far from having it working. After you instantiate correct type of object based on the presence or absence of the condition property in the JSON, you can populate the instance using the serializer.Populate method. This should take care of the recursion automatically. You do need to pass a new JsonReader instance to Populate, which you can create using jo.CreateReader().
Here is what the converter should look like:
public class RuleConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Rule).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jo = JObject.Load(reader);
Rule rule = null;
if (jo["condition"] == null)
{
rule = new Rule();
}
else
{
rule = new Condition();
}
serializer.Populate(jo.CreateReader(), rule);
return rule;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here is a working example: https://dotnetfiddle.net/SHctMo

ASP.NET RC2 - ModelState doesn't validate elements of collection

Let's say that I have simple model with required attribute above property.
public class User
{
[Required]
string Name {get;set;}
string Surname {get;set;}
}
When I POST/PUT only one instance of User and Name is empty it works pretty well. ModelState is not valid and contains error.
When I POST/PUT collection of objects User and in some of them Name is empty then ModelState is valid and it does not contain any validation errors.
Could you tell me what is wrong with it and why it concerns only collections? I noticed same behaviour when I have one object with relation one-many. Then collection within this object also is not validated by ModelState.
I don't want to validate required fields manually, it should work automatically.
You need to create a ActionFilter
public class ModelStateValidActionFilter : IAsyncActionFilter
{
public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// Validate ICollection
if (context.ActionArguments.Count == 1 && context.ActionArguments.First().Value.GetType().IsListType())
{
foreach (var arg in (IList)context.ActionArguments.First().Value )
{
var parameters = arg.GetType().GetProperties();
foreach (var parameter in parameters)
{
var argument = context.ActionArguments.GetOrDefault(parameter.Name);
EvaluateValidationAttributes(parameter, argument, context.ModelState);
}
}
}
if (context.ModelState.IsValid)
{
return next();
}
context.Result = new BadRequestObjectResult(context.ModelState);
return Task.CompletedTask;
}
private void EvaluateValidationAttributes(PropertyInfo parameter, object argument, ModelStateDictionary modelState)
{
var validationAttributes = parameter.CustomAttributes;
foreach (var attributeData in validationAttributes)
{
var attributeInstance = parameter.GetCustomAttribute(attributeData.AttributeType);
var validationAttribute = attributeInstance as ValidationAttribute;
if (validationAttribute != null)
{
var isValid = validationAttribute.IsValid(argument);
if (!isValid)
{
modelState.AddModelError(parameter.Name, validationAttribute.FormatErrorMessage(parameter.Name));
}
}
}
}
and add it into your MVC options
services.AddMvc()
.AddMvcOptions(opts =>
{
opts.Filters.Add(new ModelStateValidActionFilter());
}

Create RSS feed in MVC4/WebAPI

I'm looking for the best way to create an RSS feed via MVC4 (and/or WebAPI). This post seemed the most applicable http://www.strathweb.com/2012/04/rss-atom-mediatypeformatter-for-asp-net-webapi/. But it was written in the pre-Release days of WebAPI. I've used Nuget to bring all packages up-to-date but attempting to build the project tosses:
Error 2 The type or namespace name 'FormatterContext' could not be found (are you missing a using directive or an assembly reference?) G:\Code\MvcApplication-atomFormatter\MvcApplication-atomFormatter\SyndicationFeedFormatter.cs 38 129 MvcApplication_syndicationFeedFormatter
I've found a number of articles explaining that the MediaTypeFormatter has changed significantly since beta but I have found details on the adjustments required to the code snippet in question.
Is there an updated resource showing the construction of an RSSFormatter?
thx
Yes I wrote that tutorial against Beta.
Below is the code updated to RTM version.
One advice, if I may, is that this example uses a simple "whitelist" of concrete types for which RSS/Atom feed is build (in this case my Url model). Ideally in more complex scenarios, you'd have the formatter set up against an interface, rather than a concrete type, and have all Models which are supposed to be exposed as RSS to implement that interface.
Hope this helps.
public class SyndicationFeedFormatter : MediaTypeFormatter
{
private readonly string atom = "application/atom+xml";
private readonly string rss = "application/rss+xml";
public SyndicationFeedFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue(atom));
SupportedMediaTypes.Add(new MediaTypeHeaderValue(rss));
}
Func<Type, bool> SupportedType = (type) =>
{
if (type == typeof(Url) || type == typeof(IEnumerable<Url>))
return true;
else
return false;
};
public override bool CanReadType(Type type)
{
return SupportedType(type);
}
public override bool CanWriteType(Type type)
{
return SupportedType(type);
}
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, System.Net.Http.HttpContent content, System.Net.TransportContext transportContext)
{
return Task.Factory.StartNew(() =>
{
if (type == typeof(Url) || type == typeof(IEnumerable<Url>))
BuildSyndicationFeed(value, writeStream, content.Headers.ContentType.MediaType);
});
}
private void BuildSyndicationFeed(object models, Stream stream, string contenttype)
{
List<SyndicationItem> items = new List<SyndicationItem>();
var feed = new SyndicationFeed()
{
Title = new TextSyndicationContent("My Feed")
};
if (models is IEnumerable<Url>)
{
var enumerator = ((IEnumerable<Url>)models).GetEnumerator();
while (enumerator.MoveNext())
{
items.Add(BuildSyndicationItem(enumerator.Current));
}
}
else
{
items.Add(BuildSyndicationItem((Url)models));
}
feed.Items = items;
using (XmlWriter writer = XmlWriter.Create(stream))
{
if (string.Equals(contenttype, atom))
{
Atom10FeedFormatter atomformatter = new Atom10FeedFormatter(feed);
atomformatter.WriteTo(writer);
}
else
{
Rss20FeedFormatter rssformatter = new Rss20FeedFormatter(feed);
rssformatter.WriteTo(writer);
}
}
}
private SyndicationItem BuildSyndicationItem(Url u)
{
var item = new SyndicationItem()
{
Title = new TextSyndicationContent(u.Title),
BaseUri = new Uri(u.Address),
LastUpdatedTime = u.CreatedAt,
Content = new TextSyndicationContent(u.Description)
};
item.Authors.Add(new SyndicationPerson() { Name = u.CreatedBy });
return item;
}
}

Unity.BuildUp unable to disambiguate

I have a class with two constructors, both constructors have one parameter. Due to restrictions not worth explaining I cannot alter the constructors or use a descendent class.
I can't use unity to create instances of this class because Unity sees 2 constructors with the same number of parameters and complains that it doesn't know which to use, which is fair enough. So instead I create the instance myself and then try to use UnityContainer.BuildUp()
var result = constructorInfo.Invoke(new object[] { content });
UnitContainer.BuildUp(result);
The above code does not set any of my [Dependency] properties nor does it call an [InjectionMethod] if I use that instead.
var result = constructorInfo.Invoke(new object[] { content });
UnitContainer.BuildUp(typeOfObject, result);
This throws another exception about ambiguous constructors, even though I am not asking it to construct the instance.
Does anyone have any ideas?
Here is an example app
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.Unity;
using System.Reflection;
namespace ConsoleApplication7
{
public interface IConstructorType1 { }
public interface IConstructorType2 { }
public interface INeedThisDependency { }
public class NeedThisDependency : INeedThisDependency { }
public class MyDomainObject
{
public MyDomainObject(IConstructorType1 constructorType1) { }
public MyDomainObject(IConstructorType2 constructorType2) { }
[Dependency]
public INeedThisDependency Needed { get; set; }
}
class Program
{
static void Main(string[] args)
{
IUnityContainer unityContainer = new UnityContainer();
unityContainer.RegisterType<INeedThisDependency, NeedThisDependency>();
//Try with type 1 constructor
ConstructorInfo constructorInfo1 = typeof(MyDomainObject).GetConstructor(new Type[] { typeof(IConstructorType1) });
MyDomainObject instance1 = CreateTheInstance(unityContainer, typeof(MyDomainObject), constructorInfo1, null);
//Try with type 2 constructor
ConstructorInfo constructorInfo2 = typeof(MyDomainObject).GetConstructor(new Type[] { typeof(IConstructorType2) });
MyDomainObject instance2 = CreateTheInstance(unityContainer, typeof(MyDomainObject), constructorInfo2, null);
}
//This is the only point I have any influence over what happens
//So this is the only place I get to change the code.
static MyDomainObject CreateTheInstance(IUnityContainer unityContainer, Type type, ConstructorInfo constructorInfo, object parameters)
{
var result = (MyDomainObject)constructorInfo.Invoke(new object[] { parameters });
//This will throw an ambiguous constructor exception,
//even though I am not asking it to construct anything
unityContainer.BuildUp(type, result);
//This will not build up dependencies
unityContainer.BuildUp(result);
if (result.Needed == null)
throw new NullReferenceException("Needed");
return result;
}
}
}
It's a bug in BuildUp, unfortunately.
Instead of calling BuildUp call this CallInjectionMethod helper.
public static class UnityContainerHelper
{
public static void CallInjectionMethod(this IUnityContainer unityContainer, object instance, params ResolverOverride[] overrides)
{
if (instance == null)
throw new ArgumentNullException("Instance");
var injectionMethodInfo = instance.GetType().GetMethods().Where(x => x.GetCustomAttributes(typeof(InjectionMethodAttribute), true).Any()).SingleOrDefault();
if (injectionMethodInfo == null)
return;
var parameters = injectionMethodInfo.GetParameters();
if (parameters.Length == 0)
return;
var dependencies = new object[parameters.Length];
int index = 0;
foreach (Type parameterType in parameters.Select(x => x.ParameterType))
{
dependencies[index] = unityContainer.Resolve(parameterType, overrides);
index++;
}
injectionMethodInfo.Invoke(instance, dependencies);
}
}

Resources