【问题标题】:JSON.Net custom contract serialization and CollectionsJSON.Net 自定义合约序列化和集合
【发布时间】:2016-10-28 13:44:12
【问题描述】:

我正在尝试创建一个 IContractResolver 来简化我对 WebApi 项目的安全处理。

我正在尝试什么:

我想根据一组动态条件(例如调用端点的用户的角色)序列化某些对象/属性。

所以我实现了一个在接口的 CreateProperty 覆盖中检查的自定义属性,并将 ShouldSerialize 函数设置为我自己的逻辑。

我现在的问题是,是否可以有条件地序列化某个列表中的完整对象?而不是在预处理步骤中过滤列表(如果我更改对象,这很容易出错)我希望它由当前的 ContractResolver 递归处理。

在某种程度上,我试图得到这样的东西:

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

我错过了覆盖,这根本不可能吗?有没有更好的方法来实际执行此操作,而不必“预解析”我的所有值?

【问题讨论】:

标签: c# json serialization asp.net-web-api json.net


【解决方案1】:

这不是开箱即用的。如果您检查 JsonSerializerInternalWriter.SerializeList() 的源代码,您会发现没有逻辑可以根据某些过滤器跳过集合条目。

但是,Json.NET 确实有 robust exception handling。如果在开始序列化对象时抛出异常,然后在[OnError] 回调中捕获并吞下:

  • 如果写入数组条目,则数组条目将被跳过(您希望的行为)。
  • 如果写入根对象,则不会捕获异常(可能是错误?)
  • 否则会写入null。

因此,实现所需功能的一种可能性是从自定义合约解析器添加到 JsonContract.OnSerializingCallbacks 的人工回调中抛出异常,然后使用添加到 JsonContract.OnErrorCallbacks 的处理程序捕获并吞下异常。当与您已经在做的对属性值进行过滤结合使用时,这种方法的优点是保证即使它是根对象或包含在字典、动态对象或多维数组中的秘密对象也不能被序列化。这种方法不会干扰PreserveReferencesHandling.Arrays。

执行此操作的一个合同解析器如下:

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;
    }
}

那么一种可能的用途是:

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("");
    }
}

原型fiddle。

请注意,抛出和捕获大量异常可能会对性能产生影响。见How expensive are exceptions in C#?。您将需要分析您的 Web 应用程序以确定这是否是一个问题。您还需要决定您的 Web 服务在尝试序列化“秘密”根对象时是否应该返回异常,或者执行其他操作。

【讨论】:

  • 这是一个完美的答案,可惜Json.Net不能跳过集合对象。但你的方式可能会完美。我需要分析性能以查看这是否适用于我的用例,但这正是我所要求的。
猜你喜欢
  • 2011-11-25
  • 2012-11-29
  • 2019-01-29
  • 1970-01-01
  • 1970-01-01
  • 2015-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多