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

时间:2023-04-25
本文介绍了在返回给客户端之前修改 JSON 的通用方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着跟版网的小编来一起学习吧!

问题描述

我正在寻找一种通用方法,该方法允许我修改返回给客户端的对象的 JSON,特别是删除返回对象中的某些属性.类似于 这里的建议.

I'm after a generic method that allows me to modify the JSON of an object being returned to the client, specifically the removal of certain properties in returned objects. Similar to what is suggested here.

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

The modifications are non-deterministic in that they are determined per-request, based on rules associated with the user. So this not suited to a method that is cached.

我已经查看了几种方法.最明显的选择是 JsonConverter,但是这有一些问题,如 here,这里和这里.

I've reviewed several methods. The most obvious choice would be a JsonConverter, however there are problems with this, as listed here, here and here.

这种方式的主要问题是在WriteJson中调用JToken.FromObject获取特定值的JSON,递归调用同一个JsonConverter,导致循环.

The main problem with this approach is that calling JToken.FromObject in WriteJson to get the JSON for the specific value, recursively calls the same JsonConverter, resulting in a loop.

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

I've tried a variant of the solution listed here which provides a method of temporarily disabling CanWrite to prevent the looping issue. However it doesn't seem to work for more than one concurrent request. A single instance of the JsonConverter is being shared between multiple threads that are changing and reading the state of the CanWrite property at different times, causing inconsistent results.

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

I've also tried using a different serializer in WriteJson (i.e. other than the one supplied to the method) however this doesn't support recursion (because that serializer doesn't use my JsonConverter) so any nested items aren't processed by my JsonConverter. Removing my JsonConverter from the default serializer's converters collection has the same problem.

基本上,如果我希望能够递归处理我的模型对象,我会遇到自引用循环问题.

Basically, if I want to be able to recursively process my model object, I'm going to get the self referencing loop issue.

理想情况下,JToken.FromObject 将有某种方式选择性地不在对象本身上调用 JsonConverter,但在序列化期间仍将其应用于任何子对象.只有当传递给 CanConvert 的对象与最后一个对象传递给 WriteJson.

Ideally, JToken.FromObject would have some way of selectivly NOT calling the JsonConverter on the object itself, but still applying it to any child objects during serialization. I got half way to fixing this by modifying CanConvert to set CanWrite to true, only if the object passed to CanConvert was a different type to the last object passed to WriteJson.

但是,要使其工作,我需要一个按请求范围的 JsonConverter(出于上述相同的线程原因),但我不知道如何获得它.

However for this to work I would need a per-request scoped JsonConverter (for the same threading reasons above), but I can't see how to get that.

这是我所拥有的示例:-

Here is a sample of what I have:-

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

我考虑过的选项:-

  1. 使用自定义 JsonConverter,但手动构建 JSON 而不是利用 JToken.FromObject (增加了很多复杂性)
  2. 使用 ActionFilterAttribute 并从序列化之前的模型(我需要为每个请求修改模型对象)
  3. 在我的模型中使用 ShouldSerialzeX() 方法来执行查找(不易维护)
  4. 使用自定义的 ContractResolver (这会遭受相同的缓存问题,即使我在DefaultContractResolver 将shareCache"设置为 false)
  1. Use a custom JsonConverter, but build the JSON manually instead of leveraging JToken.FromObject (adds a lot of complexity)
  2. Using an ActionFilterAttribute and removing properties from the model prior to serialization (I'd need to use reflection for every request to modify the model object)
  3. Using ShouldSerialzeX() methods in my models that perform lookups (not easily maintainable)
  4. Using a custom ContractResolver (this suffers from the same caching issue, even if I use the now obsolete constructor in DefaultContractResolver that sets "shareCache" to false)

任何人都可以建议:-

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

提前感谢您抽出宝贵时间阅读本文.

Thanks in advance for taking the time to read this.

推荐答案

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

One possibility to fix the TestConverter for multi-threaded, multi-type scenarios would be to create a [ThreadStatic] stack of types being serialized. Then, in CanConvert, return false if the candidate type is of the same type as the type on top of the stack.

请注意,此在转换器包含在 JsonSerializerSettings.Converters.如果转换器直接应用于类或属性,例如,

Note this only works when the converter is included in JsonSerializerSettings.Converters. If the converter is applied directly to a class or property with, say,

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

那么无限递归仍然会发生,因为直接应用的转换器不会调用 CanConvert.

Then infinite recursion will still occur since CanConvert is not called for directly applied converters.

因此:

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.

In your case TBaseType would be Inua.WebApi.Authentication.IUser.

原型小提琴.

这篇关于在返回给客户端之前修改 JSON 的通用方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!