2012-02-12 8 views
14

Esto es lo que tengo:casos Pascal propiedades dinámicas con Json.NET

using Newtonsoft.Json; 

var json = "{\"someProperty\":\"some value\"}"; 
dynamic deserialized = JsonConvert.DeserializeObject(json); 

Esto funciona bien:

Assert.That(deserialized.someProperty.ToString(), Is.EqualTo("some value")); 

Quiero que esto funcione (primera letra de propiedades superior entubado) y sin cambiando json:

Assert.That(deserialized.SomeProperty.ToString(), Is.EqualTo("some value")); 

Respuesta

0

usted tiene que cambiar su JSON para, {\"SomeProperty\":\"some value\"}

+1

Gracias! Sin embargo, no tengo control sobre el JSON, así que no es posible. – dillenmeister

2

No puedo evitar sentir que no es una buena idea. Parece que estás tratando de preservar una convención de codificación, pero a costa de mantener la fidelidad entre el formato de conexión (las estructuras JSON) y tus clases de lógica. Esto puede causar confusión para los desarrolladores que esperan que se conserven las clases JSON, y puede causar problemas si también necesita, o lo necesitará en el futuro, volver a serializar estos datos en el mismo formato JSON.

Dicho esto, esto probablemente se puede lograr creando las clases .NET antes de tiempo, luego usando la sobrecarga DeserializeObject(string value, JsonSerializerSettings settings), pasándole un JsonSerializerSettings con el conjunto de propiedades Binder. También necesitará escribir un SerializationBinder personalizado en el que defina manualmente la relación entre su clase JSON y su clase .NET predefinida.

Puede ser posible generar clases de Pascal-cassette en tiempo de ejecución sin predefinirlas, pero no he profundizado lo suficiente en la implementación de JSON.NET para eso. Quizás una de las otras configuraciones de JsonSerializerSettings, como pasar CustomCreationConverter, pero no estoy seguro de los detalles.

+0

Aunque no pregunté si era una buena idea hacer esto o no, tanto usted como Martjin mencionaron que no era ... ¿así es porque es 'dinámico'? Si me deserializo a 'public class {public string SomeProperty {get; conjunto; }} '¿Todavía sentirías que era una mala idea y preferirías un estuche de camello inferior para la propiedad (' someProperty')? – dillenmeister

+0

La convención de codificación ya existe cuando decidió deserializarse en un Contrato. Sin embargo, la carpeta de MVC predeterminada hoy no distingue entre mayúsculas y minúsculas. – Aidin

+1

La convención de capitalización para propiedades en .NET es PascalCase, mientras que la convención para el sistema del que estoy obteniendo el JSON es camelCase. Creo que convertirme en PascalCase es el mejor enfoque y me sorprendió que muchos de ustedes estuvieran en contra. – dillenmeister

13

Estoy de acuerdo con Avner Shahar-Kashtan. No debe hacer esto, especialmente si no tiene control sobre el JSON.

Dicho esto, se puede hacer con el uso de un ExpandoObject y un ExpandoObjectConverter personalizado. JSON.NET ya proporciona un ExpandoObjectConverter por lo que con algunos pequeños ajustes tiene lo que desea.

Observe // CAMBIÓ comentarios dentro del fragmento de código para mostrarle dónde lo cambié.

public class CamelCaseToPascalCaseExpandoObjectConverter : JsonConverter 
{ 
    //CHANGED 
    //the ExpandoObjectConverter needs this internal method so we have to copy it 
    //from JsonReader.cs 
    internal static bool IsPrimitiveToken(JsonToken token) 
    { 
     switch (token) 
     { 
      case JsonToken.Integer: 
      case JsonToken.Float: 
      case JsonToken.String: 
      case JsonToken.Boolean: 
      case JsonToken.Null: 
      case JsonToken.Undefined: 
      case JsonToken.Date: 
      case JsonToken.Bytes: 
       return true; 
      default: 
       return false; 
     } 
    } 

/// <summary> 
/// Writes the JSON representation of the object. 
/// </summary> 
/// <param name="writer">The <see cref="JsonWriter"/> to write to.</param> 
/// <param name="value">The value.</param> 
/// <param name="serializer">The calling serializer.</param> 
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
{ 
    // can write is set to false 
} 

/// <summary> 
/// Reads the JSON representation of the object. 
/// </summary> 
/// <param name="reader">The <see cref="JsonReader"/> to read from.</param> 
/// <param name="objectType">Type of the object.</param> 
/// <param name="existingValue">The existing value of object being read.</param> 
/// <param name="serializer">The calling serializer.</param> 
/// <returns>The object value.</returns> 
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
{ 
    return ReadValue(reader); 
} 

private object ReadValue(JsonReader reader) 
{ 
    while (reader.TokenType == JsonToken.Comment) 
    { 
    if (!reader.Read()) 
     throw new Exception("Unexpected end."); 
    } 

    switch (reader.TokenType) 
    { 
    case JsonToken.StartObject: 
     return ReadObject(reader); 
    case JsonToken.StartArray: 
     return ReadList(reader); 
    default: 
     //CHANGED 
     //call to static method declared inside this class 
     if (IsPrimitiveToken(reader.TokenType)) 
     return reader.Value; 

     //CHANGED 
     //Use string.format instead of some util function declared inside JSON.NET 
     throw new Exception(string.Format(CultureInfo.InvariantCulture, "Unexpected token when converting ExpandoObject: {0}", reader.TokenType)); 
    } 
} 

private object ReadList(JsonReader reader) 
{ 
    IList<object> list = new List<object>(); 

    while (reader.Read()) 
    { 
    switch (reader.TokenType) 
    { 
     case JsonToken.Comment: 
     break; 
     default: 
     object v = ReadValue(reader); 

     list.Add(v); 
     break; 
     case JsonToken.EndArray: 
     return list; 
    } 
    } 

    throw new Exception("Unexpected end."); 
} 

private object ReadObject(JsonReader reader) 
{ 
    IDictionary<string, object> expandoObject = new ExpandoObject(); 

    while (reader.Read()) 
    { 
    switch (reader.TokenType) 
    { 
     case JsonToken.PropertyName: 
     //CHANGED 
     //added call to ToPascalCase extension method  
     string propertyName = reader.Value.ToString().ToPascalCase(); 

     if (!reader.Read()) 
      throw new Exception("Unexpected end."); 

     object v = ReadValue(reader); 

     expandoObject[propertyName] = v; 
     break; 
     case JsonToken.Comment: 
     break; 
     case JsonToken.EndObject: 
     return expandoObject; 
    } 
    } 

    throw new Exception("Unexpected end."); 
} 

/// <summary> 
/// Determines whether this instance can convert the specified object type. 
/// </summary> 
/// <param name="objectType">Type of the object.</param> 
/// <returns> 
///  <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>. 
/// </returns> 
public override bool CanConvert(Type objectType) 
{ 
    return (objectType == typeof (ExpandoObject)); 
} 

/// <summary> 
/// Gets a value indicating whether this <see cref="JsonConverter"/> can write JSON. 
/// </summary> 
/// <value> 
///  <c>true</c> if this <see cref="JsonConverter"/> can write JSON; otherwise, <c>false</c>. 
/// </value> 
public override bool CanWrite 
{ 
    get { return false; } 
} 
} 

Una cadena simple para el convertidor de Pascal Case. Hazlo más inteligente si lo necesitas.

public static class StringExtensions 
{ 
    public static string ToPascalCase(this string s) 
    { 
     if (string.IsNullOrEmpty(s) || !char.IsLower(s[0])) 
      return s; 

     string str = char.ToUpper(s[0], CultureInfo.InvariantCulture).ToString((IFormatProvider)CultureInfo.InvariantCulture); 

     if (s.Length > 1) 
      str = str + s.Substring(1); 

     return str; 
    } 
} 

Ahora puede usarlo así.

var settings = new JsonSerializerSettings() 
        { 
         ContractResolver = new CamelCasePropertyNamesContractResolver(), 
         Converters = new List<JsonConverter> { new CamelCaseToPascalCaseExpandoObjectConverter() } 
        }; 

var json = "{\"someProperty\":\"some value\"}"; 

dynamic deserialized = JsonConvert.DeserializeObject<ExpandoObject>(json, settings); 

Console.WriteLine(deserialized.SomeProperty); //some value 

var json2 = JsonConvert.SerializeObject(deserialized, Formatting.None, settings); 

Console.WriteLine(json == json2); //true 

El ContractResolverCamelCasePropertyNamesContractResolver se utiliza cuando serializar el objeto de vuelta a JSON y lo hace caso Camel de nuevo. Esto también lo proporciona JSON.NET. Si no necesitas esto, puedes omitirlo.

+0

Ok, parece que esta es la respuesta! Sin embargo, esperaba que hubiera un mejor punto de extensión que este ... Voy a votar ahora y marcar como respondido cuando lo probé. ¡Gracias! – dillenmeister

+10

Excelente código. Aunque estoy completamente en desacuerdo con el comentario de que "no deberías estar haciendo esto". Si el json proviene de un cliente de JavaScript, probablemente este desacuerdo de convención ocurrirá. La convención de Javascript para las propiedades es camelCase. La convención de C# para las propiedades es Pascal. Tiene que ser convertido en algún momento. –

+0

Esta es una excelente solución. Creo que hay casos de uso donde la conversión al caso Pascal es una práctica aceptable. En mi caso, una propiedad en una clase era dinámica. El resultado fue un caso pascal para tipos no dinámicos y un caso camel para el dinámico y sus subpropiedades. También modifiqué el método 'CanConvert' para usar 'return (objectType == typeof (Object));', habilitando la asignación a propiedades dinámicas. – gb2d

0

Para newtonsoft agregar este atributo a sus propiedades:

[JsonProperty("schwabFirmId")] 

Una opción más simple (ya que sólo tiene que hacerlo una vez por clase) si usted está listo para incluyendo MongoDB: trate de añadir una referencia a MongoDB. Bson.Serialization.Conventions.

A continuación, añadir esto en su constructor de modelo:

var pack = new ConventionPack { new CamelCaseElementNameConvention(), new IgnoreIfDefaultConvention(true) }; 
      ConventionRegistry.Register("CamelCaseIgnoreDefault", pack, t => true); 

Cualquiera de los dos mantener sus propiedades favoritas C# PascalCased y su JSON CamelCased.

La deserialización tratará los datos de entrada como PascalCased y la serialización lo cambiará a camelCase.