c# - 子属性的 JSON .NET 自定义名称解析器

标签 c# json mongodb json.net

我有一个 API,它从 MongoDB 返回一个 JSON 对象,其中属性之一是“开放式”文档,这意味着它可以是该属性的任何有效 JSON。我事先不知道属性的名称是什么,它们可以是任何字符串。我只知道这个特定的属性需要按照它在数据库中的存储方式进行序列化。因此,如果最初存储的属性名称是“Someproperty”,则 JSON 中的序列化响应需要是“Someproperty”,而不是“someProperty”。

我们有这样的配置:

ContractResolver = new CamelCasePropertyNamesContractResolver();

在我们的 CustomJsonSerializer 中,但在返回“开放式”JSON 时,它会扰乱响应的格式。它对所有这些属性进行驼峰式封装,而实际上我们希望响应与它们在 MongoDB (BSON) 中的存储方式完全相同。我知道这些值在通过数据库存储/检索时会保持大小写,所以这不是问题。

如何告诉 JSON.net 从本质上绕过特定数据点的所有子属性的 CamelCasePropertyNameResolver?

编辑: 只是为了提供更多信息,并分享我已经尝试过的内容:

我考虑过像这样重写 PropertyNameResolver:

protected override string ResolvePropertyName(string propertyName)
{
      if (propertyName.ToLower().Equals("somedocument"))
      {
                return propertyName;
      }
      else return base.ResolvePropertyName(propertyName);
}

但是,如果我有这样的 JSON 结构:

{
   "Name" : "MyObject",
   "DateCreated" : "11/14/2016",
   "SomeDocument" : 
   {
      "MyFirstProperty" : "foo",
      "mysecondPROPERTY" : "bar",
      "another_random_subdoc" : 
      {
         "evenmoredata" : "morestuff"
      }
   }
}

那么我需要所有属性(任何子属性的名称)保持原样。我发布的上述覆盖(我相信)只会忽略与“somedocument”的精确匹配,并且仍然会驼峰式命名所有子属性。

最佳答案

您可以做的是,对于相关属性,创建一个 custom JsonConverter,使用通过不同的合约解析器创建的不同 JsonSerializer 来序列化相关属性值,如下所示:

public class AlternateContractResolverConverter : JsonConverter
{
    [ThreadStatic]
    static Stack<Type> contractResolverTypeStack;

    static Stack<Type> ContractResolverTypeStack { get { return contractResolverTypeStack = (contractResolverTypeStack ?? new Stack<Type>()); } }

    readonly IContractResolver resolver;

    JsonSerializerSettings ExtractAndOverrideSettings(JsonSerializer serializer)
    {
        var settings = serializer.ExtractSettings();
        settings.ContractResolver = resolver;
        settings.CheckAdditionalContent = false;
        if (settings.PreserveReferencesHandling != PreserveReferencesHandling.None)
        {
            // Log an error throw an exception?
            Debug.WriteLine(string.Format("PreserveReferencesHandling.{0} not supported", serializer.PreserveReferencesHandling));
        }
        return settings;
    }

    public AlternateContractResolverConverter(Type resolverType)
    {
        if (resolverType == null)
            throw new ArgumentNullException("resolverType");
        resolver = (IContractResolver)Activator.CreateInstance(resolverType);
        if (resolver == null)
            throw new ArgumentNullException(string.Format("Resolver type {0} not found", resolverType));
    }

    public override bool CanRead { get { return ContractResolverTypeStack.Count == 0 || ContractResolverTypeStack.Peek() != resolver.GetType(); } }
    public override bool CanWrite { get { return ContractResolverTypeStack.Count == 0 || ContractResolverTypeStack.Peek() != resolver.GetType(); } }

    public override bool CanConvert(Type objectType)
    {
        throw new NotImplementedException("This contract resolver is intended to be applied directly with [JsonConverter(typeof(AlternateContractResolverConverter), typeof(SomeContractResolver))] or [JsonProperty(ItemConverterType = typeof(AlternateContractResolverConverter), ItemConverterParameters = ...)]");
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        using (ContractResolverTypeStack.PushUsing(resolver.GetType()))
            return JsonSerializer.CreateDefault(ExtractAndOverrideSettings(serializer)).Deserialize(reader, objectType);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        using (ContractResolverTypeStack.PushUsing(resolver.GetType()))
            JsonSerializer.CreateDefault(ExtractAndOverrideSettings(serializer)).Serialize(writer, value);
    }
}

internal static class JsonSerializerExtensions
{
    public static JsonSerializerSettings ExtractSettings(this JsonSerializer serializer)
    {
        // There is no built-in API to extract the settings from a JsonSerializer back into JsonSerializerSettings,
        // so we have to fake it here.
        if (serializer == null)
            throw new ArgumentNullException("serializer");
        var settings = new JsonSerializerSettings
        {
            CheckAdditionalContent = serializer.CheckAdditionalContent,
            ConstructorHandling = serializer.ConstructorHandling,
            ContractResolver = serializer.ContractResolver,
            Converters = serializer.Converters,
            Context = serializer.Context,
            Culture = serializer.Culture,
            DateFormatHandling = serializer.DateFormatHandling,
            DateFormatString = serializer.DateFormatString,
            DateParseHandling = serializer.DateParseHandling,
            DateTimeZoneHandling = serializer.DateTimeZoneHandling,
            DefaultValueHandling = serializer.DefaultValueHandling,
            EqualityComparer = serializer.EqualityComparer,
            // No Get access to the error event, so it cannot be copied.
            // Error = += serializer.Error
            FloatFormatHandling = serializer.FloatFormatHandling,
            FloatParseHandling = serializer.FloatParseHandling,
            Formatting = serializer.Formatting,
            MaxDepth = serializer.MaxDepth,
            MetadataPropertyHandling = serializer.MetadataPropertyHandling,
            MissingMemberHandling = serializer.MissingMemberHandling,
            NullValueHandling = serializer.NullValueHandling,
            ObjectCreationHandling = serializer.ObjectCreationHandling,
            ReferenceLoopHandling = serializer.ReferenceLoopHandling,
            // Copying the reference resolver doesn't work in the default case, since the
            // actual BidirectionalDictionary<string, object> mappings are held in the 
            // JsonSerializerInternalBase.
            // See https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/DefaultReferenceResolver.cs
            ReferenceResolverProvider = () => serializer.ReferenceResolver,
            PreserveReferencesHandling = serializer.PreserveReferencesHandling,
            StringEscapeHandling = serializer.StringEscapeHandling,
            TraceWriter = serializer.TraceWriter,
            TypeNameHandling = serializer.TypeNameHandling,
            // Changes in Json.NET 10.0.1
            //TypeNameAssemblyFormat was obsoleted and replaced with TypeNameAssemblyFormatHandling in Json.NET 10.0.1
            //TypeNameAssemblyFormat = serializer.TypeNameAssemblyFormat,
            TypeNameAssemblyFormatHandling = serializer.TypeNameAssemblyFormatHandling,
            //Binder was obsoleted and replaced with SerializationBinder in Json.NET 10.0.1
            //Binder = serializer.Binder,
            SerializationBinder = serializer.SerializationBinder,
        };
        return settings;
    }
}

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

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

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

然后像这样使用它:

public class RootObject
{
    public string Name { get; set; }
    public DateTime DateCreated { get; set; }

    [JsonProperty(NamingStrategyType = typeof(DefaultNamingStrategy))]
    [JsonConverter(typeof(AlternateContractResolverConverter), typeof(DefaultContractResolver))]
    public SomeDocument SomeDocument { get; set; }
}

public class SomeDocument
{
    public string MyFirstProperty { get; set; }
    public string mysecondPROPERTY { get; set; }
    public AnotherRandomSubdoc another_random_subdoc { get; set; }
}

public class AnotherRandomSubdoc
{
    public string evenmoredata { get; set; }
    public DateTime DateCreated { get; set; }
}

(在这里,我假设您希望逐字序列化 "SomeDocument" 属性名称,即使您的问题并不完全清楚。为此,我使用 JsonPropertyAttribute.NamingStrategyType Json.NET 9.0.1。如果您使用的是早期版本,则需要显式设置属性名称。)

那么生成的 JSON 将是:

{
  "name": "Question 40597532",
  "dateCreated": "2016-11-14T05:00:00Z",
  "SomeDocument": {
    "MyFirstProperty": "my first property",
    "mysecondPROPERTY": "my second property",
    "another_random_subdoc": {
      "evenmoredata": "even more data",
      "DateCreated": "2016-11-14T05:00:00Z"
    }
  }
}

请注意,此解决方案不适用于 preserving object references 。如果您需要它们一起工作,您可能需要考虑一种基于堆栈的方法,类似于 Json.NET serialize by depth and attribute 中的方法

演示 fiddle here

顺便说一句,您是否考虑过将此 JSON 存储为原始字符串文字,如 this question 的答案所示?

关于c# - 子属性的 JSON .NET 自定义名称解析器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40597532/

相关文章:

c# - WebRequest.Create 抛出 IOException "The specified registry key does not exist."

java - jax rs 数据转换

json - Node.js/Hapijs - 在不命名键的情况下验证 JSON 对象有效负载中的所有键和值

java - MongoDB 嵌套文档搜索

javascript - Mongodb:如何在上限集合上创建 `tail -f` View ?

c# - 从一组 foowriter 类型的类中检索公共(public)属性

c# - 如何为所有整数类型制作扩展方法?

Node.js 和 Mongodb

c# - 如何以编程方式检测哪个程序捕获了我的文件

javascript - AngularJS 用 <select> 中的对象填充 $scope.selected