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

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

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

我已经回顾了几种方法。 最明显的选择是JsonConverter,但是这里存在问题,如此处和此处所列。

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

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

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

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

理想情况下, JToken.FromObject有一些方法可以选择性地不在对象本身上调用JsonConverter,但仍然在序列化期间将它应用于任何子对象。 只有在传递给CanConvert的对象与传递给CanConvert的最后一个对象的类型不同时,我才通过修改CanConvertCanWrite设置为true来解决这个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(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并从模型中删除属性(我需要对每个修改模型对象的请求使用reflection)
  3. 在我的模型中使用ShouldSerialzeX()方法执行查找(不易维护)
  4. 使用自定义ContractResolver (这会遇到相同的缓存问题,即使我在DefaultContractResolver中使用现在过时的构造函数将“shareCache”设置为false)

谁能建议: –

  • 一种根据请求制作JsonConverters的方法
  • 假设无法按请求进行,则可以通过JsonConverter修复线程问题
  • JsonConverter的替代方法,允许我在将JSON对象返回到不依赖于大量reflection开销的客户端之前对其进行全局检查和修改
  • 别的什么?

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

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

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

  [JsonConverter(typeof(TestConverter))] 

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

从而:

 public class TestConverter : JsonConverter { [ThreadStatic] static Stack typeStack; static Stack TypeStack { get { return typeStack = (typeStack ?? new Stack()); } } 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 : IDisposable { readonly Stack stack; public PushValue(T value, Stack 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(this Stack stack) { if (stack == null) throw new ArgumentNullException(); if (stack.Count == 0) return default(T); return stack.Peek(); } public static PushValue PushUsing(this Stack stack, T value) { if (stack == null) throw new ArgumentNullException(); return new PushValue(value, stack); } } 

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

原型小提琴 。

以典型的方式,提出问题的过程使我对问题采取了新的看法。

我找到了一种可能的解决方法:创建自定义MediaTypeFormatter。

在这里和这里的帮助下,一个潜在的解决方案: –

 using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http.Formatting; using System.Text; using System.Threading.Tasks; namespace Test { public class TestFormatter : MediaTypeFormatter { public TestFormatter() { SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("application/json")); } public override bool CanReadType(Type type) { return false; } public override bool CanWriteType(Type type) { return true; } public override Task WriteToStreamAsync(Type type, object value, System.IO.Stream writeStream, System.Net.Http.HttpContent content, System.Net.TransportContext transportContext) { JsonSerializer serializer = new JsonSerializer(); serializer.ContractResolver = new CamelCasePropertyNamesContractResolver(); serializer.Converters.Add(new TestConverter()); return Task.Factory.StartNew(() => { using (JsonTextWriter jsonTextWriter = new JsonTextWriter(new StreamWriter(writeStream, Encoding.ASCII)) { CloseOutput = false }) { serializer.Serialize(jsonTextWriter, value); jsonTextWriter.Flush(); } }); } } } 

然后配置应用程序以使用它: –

 // insert at 0 so it runs before System.Net.Http.Formatting.JsonMediaTypeFormatter config.Formatters.Insert(0, new TestFormatter()); 

这为每个请求创建了一个新的JsonConverter实例,结合原始post中的其他修复,似乎解决了这个问题。

这可能不是最好的方法,所以我会留下这个更好的建议,或直到我意识到为什么这不起作用。