sql >> Base de Datos >  >> NoSQL >> MongoDB

Solucionador de nombres personalizados JSON .NET para subpropiedades

Lo que puede hacer es, para la propiedad en cuestión, crear un custom JsonConverter que serializa el valor de la propiedad en cuestión utilizando un JsonSerializer creado con una resolución de contrato diferente, así:

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

Entonces úsalo así:

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

(Aquí asumo que desea el "SomeDocument" el nombre de la propiedad se serializará palabra por palabra, aunque no quedó del todo claro en su pregunta. Para hacer eso, estoy usando JsonPropertyAttribute.NamingStrategyType de Json.NET 9.0.1. Si está utilizando una versión anterior, deberá establecer el nombre de la propiedad de forma explícita).

Entonces el JSON resultante será:

{
  "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"
    }
  }
}

Tenga en cuenta que esta solución NO funciona bien con preservando referencias de objetos . Si necesita que trabajen juntos, es posible que deba considerar un enfoque basado en pilas similar al de Json.NET serializar por profundidad y atributo

Demostración de violín aquí .

Por cierto, ¿ha considerado almacenar este JSON como una cadena literal sin formato, como en la respuesta a esta pregunta ?