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

时间:2016-11-14 20:45:11

标签: c# json mongodb json.net

我有一个API从MongoDB返回一个JSON对象,其中一个属性是"开放式"文档,意味着它可以是该属性的任何有效JSON。我不知道属性的名称是提前的,它们可以是任何字符串。我只知道这个特定的属性需要序列化完全它是如何存储在数据库中的。因此,如果最初存储的属性名称是" Someproperty",则JSON中的序列化响应需要是" Someproperty",NOT" someProperty"。

我们有这样的配置:

ContractResolver = new CamelCasePropertyNamesContractResolver();

在我们的CustomJsonSerializer中,但在返回"开放结束时#34;它正在弄乱响应的格式。 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"的完全匹配,并且仍然会查找所有子属性。

2 个答案:

答案 0 :(得分:2)

对于相关媒体,您可以创建一个custom JsonConverter,使用使用不同合约解析程序创建的其他JsonSerializer来序列化相关属性值,如下所示:

public class AlternateContractResolverConverter : JsonConverter
{
    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 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)
    {
        return JsonSerializer.CreateDefault(ExtractAndOverrideSettings(serializer)).Deserialize(reader, objectType);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        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
        {
            Binder = serializer.Binder,
            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,
            TypeNameAssemblyFormat = serializer.TypeNameAssemblyFormat,
            TypeNameHandling = serializer.TypeNameHandling,
        };
        return settings;
    }
}

然后像这样使用它:

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"属性名称逐字序列化,即使您的问题并不完全清楚。为此,我正在使用Json的JsonPropertyAttribute.NamingStrategyType .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

的基于堆栈的方法

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

答案 1 :(得分:1)

我认为你应该向后看这个。

不要试图触摸您不知道的属性,而是将其作为默认行为,触摸您知道的

换句话说,请勿使用CamelCasePropertyNamesContractResolver。适当地处理你熟悉的属性,让其他属性透明地通过。