我正在寻找一种通用方法,该方法允许我修改返回给客户端的对象的 JSON,特别是删除返回对象中的某些属性。类似于建议here .
修改是不确定的,因为它们是根据与用户关联的规则根据请求确定的。所以这不适合缓存的方法。
我已经回顾了几种方法。最明显的选择是 JsonConverter,但是这也有问题,如所列 here , here和 here .
这种方式的主要问题是在WriteJson
中调用JToken.FromObject
获取特定值的JSON,递归调用同一个JsonConverter,导致循环.
我已经尝试了 here 中列出的解决方案的一个变体它提供了一种暂时禁用 CanWrite
以防止循环问题的方法。但是,它似乎不适用于多个并发请求。 JsonConverter 的单个实例在多个线程之间共享,这些线程在不同时间更改和读取 CanWrite 属性的状态,从而导致不一致的结果。
我也试过在 WriteJson
中使用不同的序列化器(即除了提供给方法的序列化器)但是这不支持递归(因为那个序列化器不使用我的 JsonConverter)所以我的 JsonConverter 不会处理任何嵌套项目。从默认序列化程序的转换器集合中删除我的 JsonConverter 有同样的问题。
基本上,如果我想能够递归地处理我的模型对象,我就会遇到自引用循环问题。
理想情况下,JToken.FromObject
可以通过某种方式有选择地不对对象本身调用 JsonConverter,但仍会在序列化期间将其应用于任何子对象。我通过修改 CanConvert
将 CanWrite
设置为 true 来解决这个问题,前提是传递给 CanConvert
的对象与传递给 WriteJson
的最后一个对象。
然而,为了让它工作,我需要一个按请求范围的 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 (这会受到相同缓存的影响 问题,即使我在 将“shareCache”设置为 false 的 DefaultContractResolver)
任何人都可以建议:-
- 一种根据请求制作 JsonConverter 的方法
- 假设它不能按请求进行,这是一种解决 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
。
原型(prototype) fiddle .
关于c# - 在返回给客户端之前修改 JSON 的通用方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35532466/