Individual serialization of JSON.Net contracts and collections

I am trying to create an IContractResolver to simplify my security handling in a WebApi project.

What am I trying:

I want to serialize specific objects / properties based on a set of dynamic conditions (e.g. Role of the user who called the endpoint).

So, I applied the custom attribute that is noted in the CreateProperty interface override, and set the ShouldSerialize function to my own logic.

Now my question is: is it possible to conditionally serialize complete objects that are on a specific list? Instead of filtering lists at the preprocessing stage (which is error prone if I modify my objects), I would like it to be processed recursively by the current ContractResolver.

To some extent, I tried to get something like this:

override void CreateObject(JSONObject ob){ if ( ob.DeclaringType == MyType) { ob.ShouldSerialize = instance => {[...] }; //Custom Logic } } 

I miss redefinition, is that even impossible? Is there a better way to actually do this without having to โ€œpre-parseโ€ all my values?

0
json c # serialization asp.net-web-api
source share
1 answer

It is not implemented out of the box. If you check the source for JsonSerializerInternalWriter.SerializeList() , you will see that there is no logic to skip collection entries based on some filter.

However, Json.NET has robust exception handling . If an exception occurs when the object is serialized, then it is caught and swallowed in the [OnError] :

  • If you record an array record, the array record is skipped (your desired behavior).
  • If you write the root object, the exception does not get caught (maybe an error?)
  • Otherwise, null written.

So one way to achieve the desired functionality would be to throw an exception from the artificial callback added to JsonContract.OnSerializingCallbacks your custom contract agreement, then catch and catch the exception using a handler added to JsonContract.OnErrorCallbacks . Combined with filtering property values โ€‹โ€‹that you are already running, this approach has the advantage of ensuring that the secret object cannot be serialized even if it is the root object or when it is contained in a dictionary, dynamic object, or multidimensional array . This approach will not interfere with PreserveReferencesHandling.Arrays .

One contract resolver that does this is as follows:

 sealed class JsonSkipObjectException : JsonException { } public class ShouldSerializeContractResolver : DefaultContractResolver { readonly Predicate<object> shouldSerialize; readonly SerializationCallback serializationCallback; readonly SerializationErrorCallback onErrorCallback; public ShouldSerializeContractResolver(Predicate<object> shouldSerialize) : base() { this.shouldSerialize = shouldSerialize; this.serializationCallback = (o, context) => { if (shouldSerialize != null && !this.shouldSerialize(o)) throw new JsonSkipObjectException(); }; this.onErrorCallback = (o, context, errorContext) => { if (errorContext.Error is JsonSkipObjectException) { errorContext.Handled = true; } }; } protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var property = base.CreateProperty(member, memberSerialization); if (shouldSerialize != null) { if (property.Readable) { var oldShouldSerialize = property.ShouldSerialize; property.ShouldSerialize = (o) => { if (oldShouldSerialize != null && !oldShouldSerialize(o)) return false; var value = property.ValueProvider.GetValue(o); if (!this.shouldSerialize(value)) return false; return true; }; } } return property; } protected override JsonContract CreateContract(Type objectType) { var contract = base.CreateContract(objectType); contract.OnSerializingCallbacks.Add(serializationCallback); contract.OnErrorCallbacks.Add(onErrorCallback); return contract; } } 

Then one of the possible use cases:

 public interface IConditionalSerialization { bool ShouldSerialize(); } public class ConditionalSerializationObject : IConditionalSerialization { public bool IsSecret { get; set; } public string SecretProperty { get { return "should not see me"; } } // Ensure "normal" conditional property serialization is not broken public bool ShouldSerializeSecretProperty() { return false; } #region IConditionalSerialization Members bool IConditionalSerialization.ShouldSerialize() { return !IsSecret; } #endregion } public class TestClass { public static void Test() { Predicate<object> filter = (o) => { var conditional = o as IConditionalSerialization; return conditional == null || conditional.ShouldSerialize(); }; var settings = new JsonSerializerSettings { ContractResolver = new ShouldSerializeContractResolver(filter), }; var ok = new ConditionalSerializationObject { IsSecret = false }; var notOk = new ConditionalSerializationObject { IsSecret = true }; Test(ok, settings); Test(new { Public = ok, Private = notOk }, settings); Test(new [] { ok, notOk, ok, notOk }, settings); Test(new[,] {{ ok, notOk, ok, notOk }}, settings); Test(new { Array = new[,] { { ok, notOk, ok, notOk } } }, settings); try { Test(notOk, settings); } catch (Exception ex) { Console.WriteLine("Exception thrown and not caught serializing root object " + notOk.GetType()); Console.WriteLine(ex); } } static void Test<T>(T value, JsonSerializerSettings settings) { Console.WriteLine("Unfiltered object: "); Console.WriteLine(JToken.FromObject(value)); var serializer = JsonSerializer.CreateDefault(settings); var token = JToken.FromObject(value, serializer); Console.WriteLine("Filtered object: "); Console.WriteLine(token); if (!token.SelectTokens("..IsSecret").All(t => JToken.DeepEquals(t, (JValue)false))) { throw new InvalidOperationException("token.SelectTokens(\"..IsSecret\").All(t => JToken.DeepEquals(t, (JValue)true))"); } if (token.SelectTokens("..SecretProperty").Any()) { throw new InvalidOperationException("token.SelectTokens(\"..SecretProperty\").Any()"); } Console.WriteLine("Secret objects and properties were successfully filtered."); Console.WriteLine(""); } } 

Prototype fiddle .

Note that throwing and catching a large number of exceptions can have performance implications. See How expensive are exceptions in C #? . You will need to profile your web application to determine if this is a problem. You will also need to decide if your web service should throw an exception when trying to serialize a "secret" root object or do something else.

0
source share

All Articles