c# – 在返回客户端之前修改JSON的通用方法

我正在使用一个通用方法,它允许我修改返回给客户端的对象的JSON,特别是删除返回对象中的某些属性.类似于here的建议.

这些修改是非确定性的,因为它们是根据与用户相关的规则按请求确定的.所以这不适合缓存的方法.

我已经回顾了几种方法.最明显的选择是JsonConverter,但是存在问题,如here,herehere所列.

这种方法的主要问题是调用WriteJson中的JToken.FromObject来获取特定值的JSON,递归调用相同的JsonConverter,从而产生循环.

我已经尝试了here列出的解决方案的变体,它提供了一种暂时禁用CanWrite以防止循环问题的方法.但是,它似乎不适用于多个并发请求. JsonConverter的单个实例在多个线程之间共享,这些线程在不同时间更改和读取CanWrite属性的状态,从而导致不一致的结果.

我也尝试在WriteJson中使用不同的序列化程序(即除了提供给该方法的序列化程序之外)但是这不支持递归(因为该序列化程序不使用我的JsonConverter)所以任何嵌套项目都不会由我处理JsonConverter.从默认的序列化程序的转换器集合中删除我的JsonConverter也存在同样的问题.

基本上,如果我想能够递归处理我的模型对象,我将得到自引用循环问题.

理想情况下,JToken.FromObject可以选择性地在对象本身上调用JsonConverter,但仍然在序列化期间将其应用于任何子对象.只有在传递给CanConvert的对象与传递给WriteJson的最后一个对象的类型不同时,我才通过修改CanConvert将CanWrite设置为true来解决这个问题.

但是为了实现这一点,我需要一个每请求范围的JsonConverter(由于上面相同的线程原因),但我看不出如何得到它.

以下是我的样本: –

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Test
{
    public class TestConverter : JsonConverter
    {
        bool CannotWrite { get; set; }

        public override bool CanWrite { get { return !CannotWrite; } }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            JToken token;

            //----------------------------------------

            // this works; but because it's (i think) creating a new
            // serializer inside the FromObject method
            // which means any nested objects won't get processed

            //token = JToken.FromObject(value);

            //----------------------------------------

            // this creates loop because calling FromObject will cause this
            // same JsonConverter to get called on the same object again

            //token = JToken.FromObject(value, serializer);

            //----------------------------------------

            // this gets around the loop issue, but the JsonConverter will
            // not apply to any nested objects

            //serializer.Converters.Remove(this);
            //token = JToken.FromObject(value, serializer);

            //----------------------------------------

            // see https://stackoverflow.com/a/29720068/1196867
            //
            // this works as it allows us to use the same serializer, but
            // temporarily sets CanWrite to false so the invocation of
            // FromObject doesn't cause a loop
            //
            // this also means we can't process nested objects, however
            // see below in CanConvert for a potential workaround.

            using (new PushValue<bool>(true, () => CannotWrite, (cantWrite) => CannotWrite = cantWrite))
            {
                token = JToken.FromObject(value, serializer);
            }

            // store the type of this value so we can check it in CanConvert when called for any nested objects
            this.currentType = value.GetType();

            //----------------------------------------

            // in practice this would be obtained dynamically
            string[] omit = new string[] { "Name" };

            JObject jObject = token as JObject;

            foreach (JProperty property in jObject.Properties().Where(p => omit.Contains(p.Name, StringComparer.OrdinalIgnoreCase)).ToList())
            {
                property.Remove();
            }

            token.WriteTo(writer);
        }

        private Type currentType;

        public override bool CanConvert(Type objectType)
        {
            if (typeof(Inua.WebApi.Authentication.IUser).IsAssignableFrom(objectType))
            {
                // if objectType is different to the type which is currently being processed,
                // then set CanWrite to true, so this JsonConverter will apply to any nested
                // objects that we want to process
                if (this.currentType != null && this.currentType != objectType)
                {
                    this.CannotWrite = false;
                }

                return true;
            }

            return false;
        }

        public override bool CanRead { get { return false; } }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
}

我考虑的选项: –

>使用自定义JsonConverter,但手动构建JSON而不是
利用JToken.FromObject(增加了很多复杂性)
>使用ActionFilterAttribute并从中删除属性
序列化之前的模型(我需要为每一个使用反射
请求修改模型对象)
>在我的模型中使用ShouldSerialzeX()方法执行查找(不易维护)
>使用自定义ContractResolver(这会遭受相同的缓存
问题,即使我使用现在过时的构造函数
DefaultContractResolver将“shareCache”设置为false)

谁能建议: –

>根据请求制作JsonConverters的方法
>假设不能按请求进行,这是一种修复JsonConverter线程问题的方法
> JsonConverter的替代方案,它允许我在返回到客户端之前全局检查和修改JSON对象,而不依赖于大量的反射开销
>还有别的吗?

提前感谢您花时间阅读本文.

为多线程,多类型场景修复TestConverter的一种可能性是创建一个序列化类型的[ThreadStatic]堆栈.然后,在CanConvert中,如果候选类型与堆栈顶部的类型相同,则返回false.

请注意,这仅适用于JsonSerializerSettings.Converters中包含转换器的情况.如果转换器直接应用于类或属性,例如,

    [JsonConverter(typeof(TestConverter<Inua.WebApi.Authentication.IUser>))]

然后仍然会发生无限递归,因为没有为直接应用的转换器调用CanConvert.

从而:

public class TestConverter<TBaseType> : JsonConverter
{
    [ThreadStatic]
    static Stack<Type> typeStack;

    static Stack<Type> TypeStack { get { return typeStack = (typeStack ?? new Stack<Type>()); } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        JToken token;

        using (TypeStack.PushUsing(value.GetType()))
        {
            token = JToken.FromObject(value, serializer);
        }

        // in practice this would be obtained dynamically
        string[] omit = new string[] { "Name" };

        JObject jObject = token as JObject;

        foreach (JProperty property in jObject.Properties().Where(p => omit.Contains(p.Name, StringComparer.OrdinalIgnoreCase)).ToList())
        {
            property.Remove();
        }

        token.WriteTo(writer);
    }

    public override bool CanConvert(Type objectType)
    {
        if (typeof(TBaseType).IsAssignableFrom(objectType))
        {
            return TypeStack.PeekOrDefault() != objectType;
        }

        return false;
    }

    public override bool CanRead { get { return false; } }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

public static class StackExtensions
{
    public struct PushValue<T> : IDisposable
    {
        readonly Stack<T> stack;

        public PushValue(T value, Stack<T> stack)
        {
            this.stack = stack;
            stack.Push(value);
        }

        #region IDisposable Members

        // By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
        public void Dispose()
        {
            if (stack != null)
                stack.Pop();
        }

        #endregion
    }

    public static T PeekOrDefault<T>(this Stack<T> stack)
    {
        if (stack == null)
            throw new ArgumentNullException();
        if (stack.Count == 0)
            return default(T);
        return stack.Peek();
    }

    public static PushValue<T> PushUsing<T>(this Stack<T> stack, T value)
    {
        if (stack == null)
            throw new ArgumentNullException();
        return new PushValue<T>(value, stack);
    }
}

在你的情况下,TBaseType将是Inua.WebApi.Authentication.IUser.

原型fiddle.

https://stackoverflow.com/questions/35532466/generic-method-of-modifying-json-before-being-returned-to-client

本站文章除注明转载外,均为本站原创或编译
转载请明显位置注明出处:c# – 在返回客户端之前修改JSON的通用方法