2012-02-12 3 views
14

To jest to, co mam:Pascal case właściwości dynamicznych z Json.NET

using Newtonsoft.Json; 

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

Działa to dobrze:

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

chcę to do pracy (pierwszą literę właściwościach górnego powlekanego) bez zmieniając json:

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

Odpowiedz

0

trzeba zmienić aby JSON, {\"SomeProperty\":\"some value\"}

+1

Dzięki! Nie mam wpływu na JSON, więc nie jest to możliwe. – dillenmeister

2

Nie mogę przestać myśleć, że to nie jest dobry pomysł. Wygląda na to, że próbujesz zachować konwencję kodowania, ale kosztem zachowania wierności pomiędzy formatem przewodowym (struktura JSON) a klasami logicznymi. Może to powodować zamieszanie u deweloperów, którzy oczekują zachowania klas JSON, i może powodować problemy, jeśli potrzebujesz lub będziesz potrzebować w przyszłości, aby ponownie szeregować te dane w tym samym formacie JSON.

To powiedziawszy, prawdopodobnie można to osiągnąć, tworząc klasy .NET z wyprzedzeniem, a następnie stosując przeciążenie , przekazując je JsonSerializerSettings z zestawem właściwości Binder. Będziesz również musiał napisać niestandardową SerializationBinder, w której ręcznie zdefiniujesz relację między twoją klasą JSON a predefiniowaną klasą .NET.

Możliwe jest generowanie klas Pascal-cased w środowisku wykonawczym bez ich wcześniejszego zdefiniowania, ale nie zagłębiłem się wystarczająco w implementację JSON.NET. Być może jeden z innych ustawień JsonSerializerSettings, takich jak przekazywanie CustomCreationConverter, ale nie jestem pewien szczegółów.

+0

Chociaż nie zapytałem, czy to dobry pomysł, czy to robię, czy nie, zarówno ty, jak i Martjin wspomnieliście, że to nie było ... więc czy to dlatego, że jest "dynamiczny"? Jeśli deserializowałem do 'public class {public string SomeProperty {get; zestaw; }} 'czy nadal czułbyś, że to był zły pomysł i wolisz niższy przypadek wielbłąda dla nieruchomości (' someProperty')? – dillenmeister

+0

Konwencja kodowania już istnieje, gdy zdecydujesz się na przekształcenie z postaci szeregowej w Kontrakt. Niemniej jednak domyślny segregator MVC dziś nie uwzględnia wielkości liter. – Aidin

+1

Konwencją dotyczącą wielkich liter dla właściwości w .NET jest PascalCase, podczas gdy konwencja dla systemu, z którego uzyskuję JSON, to camelCase. Myślę, że przejście na PascalCase jest najlepszym podejściem i byłem zaskoczony, że tak wielu z was było przeciwko niemu. – dillenmeister

13

Zgadzam się z Avnerem Shahar-Kashtan. Nie powinieneś robić tego, szczególnie jeśli nie masz kontroli nad JSON.

To powiedziawszy, można tego dokonać przy użyciu ExpandoObject i niestandardowego ExpandoObjectConverter. JSON.NET zapewnia już ExpandoObjectConverter, więc z niewielkimi zmianami masz to, czego potrzebujesz.

Zauważ komentarze // ZMIANA we fragmencie kodu, aby pokazać, gdzie je zmieniłem.

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

Prosty ciąg do konwertera skrzynek Pascal. Ulepsz to, jeśli potrzebujesz.

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

Teraz możesz go używać w ten sposób.

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 

The ContractResolverCamelCasePropertyNamesContractResolver jest używany podczas szeregowania obiektu z powrotem do formatu JSON i sprawia, że ​​przypadek Camel ponownie. Jest to również zapewnione przez JSON.NET. Jeśli nie potrzebujesz tego, możesz go pominąć.

+0

OK, wygląda na to, że to jest odpowiedź! Miałem nadzieję, że będzie lepszy punkt rozszerzenia niż to ... Będę głosował teraz i zaznacz jako odpowiedział, kiedy go wypróbowałem. Dzięki! – dillenmeister

+10

Doskonały kod. Chociaż całkowicie nie zgadzam się z komentarzem, że "nie powinieneś tego robić". Jeśli json pochodzi od klienta javascript, prawdopodobnie dojdzie do niezgodności konwencji. Konwencja JavaScript dla właściwości to camelCase. Konwencją C# dla właściwości jest Pascal. To musi być kiedyś nawrócone. –

+0

To doskonałe rozwiązanie. Uważam, że istnieją przypadki użycia, w których konwersja do przypadku Pascala jest akceptowalną praktyką. W moim przypadku jedna właściwość klasy była dynamiczna. Rezultatem był przypadek pascalowy dla typów niedynamicznych i wielbłąda dla właściwości dynamicznych i jego właściwości podrzędnych. Zmodyfikowałem także metodę "CanConvert", aby użyć "return (objectType == typeof (Object)) ;, umożliwiając mapowanie do właściwości dynamicznych. – gb2d

0

Dla newtonsoft dodać ten atrybut do właściwości:

[JsonProperty("schwabFirmId")] 

Prostsza opcję (ponieważ po prostu trzeba to zrobić raz w klasie), jeśli się na tym MongoDB: spróbuj dodać odniesienie do MongoDB. Bson.Serialization.Conventions.

Następnie dodać to w konstruktorze modelu:

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

Albo jeden zachowa swoje ulubione C# właściwości PascalCased a json camelCased.

Deserializowanie potraktuje dane przychodzące jako PascalCased i serializowanie zmieni go na camelCase.