使用Json.NET的Pascal案例动态属性

dil*_*ter 16 .net c# json.net

这就是我所拥有的:

using Newtonsoft.Json;

var json = "{\"someProperty\":\"some value\"}";
dynamic deserialized = JsonConvert.DeserializeObject(json);
Run Code Online (Sandbox Code Playgroud)

这很好用:

Assert.That(deserialized.someProperty.ToString(), Is.EqualTo("some value"));
Run Code Online (Sandbox Code Playgroud)

我希望这可以工作(属性大写的第一个字母)而不改变json:

Assert.That(deserialized.SomeProperty.ToString(), Is.EqualTo("some value"));
Run Code Online (Sandbox Code Playgroud)

Mar*_*n B 14

我同意Avner Shahar-Kashtan的观点.你不应该这样做,特别是如果你无法控制JSON.

也就是说,可以使用ExpandoObject和自定义ExpandoObjectConverter来完成.JSON.NET已经提供了一个ExpandoObjectConverter,所以通过一些小的调整就可以得到你想要的东西.

请注意代码段中的// CHANGED注释,以显示我更改它的位置.

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; }
}
}
Run Code Online (Sandbox Code Playgroud)

Pascal Case转换器的简单字符串.如果需要,让它变得更聪明.

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在你可以像这样使用它.

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
Run Code Online (Sandbox Code Playgroud)

ContractResolver CamelCasePropertyNamesContractResolver序列化对象的时候回JSON,再次使得骆驼情况下使用.这也是由JSON.NET提供的.如果您不需要,可以省略它.

  • 优秀的代码.虽然我完全不同意"你不应该这样做"的评论.如果json来自javascript客户端,则可能会发生此约定不匹配.Javascript的属性约定是camelCase.属性的C#约定是Pascal.它必须在某个时候进行转换. (12认同)
  • .NET Core 1.0的默认设置现在是将PascalCase C#属性转换为camelCase json属性,这与此答案具有新的相关性,我认为它非常相关. (3认同)

Avn*_*tan 3

我不禁觉得这不是一个好主意。看起来您正在尝试保留编码约定,但代价是维持有线格式(JSON 结构)和逻辑类之间的保真度。这可能会让希望保留 JSON 类的开发人员感到困惑,并且如果您还需要或将来需要将此数据重新序列化为相同的 JSON 格式,则可能会导致问题。

也就是说,这可能可以通过提前创建 .NET 类,然后使用重载DeserializeObject(string value, JsonSerializerSettings settings),将属性集传递JsonSerializerSettings给它来实现Binder。您还需要编写一个自定义SerializationBinder,在其中手动定义 JSON 类和预定义的 .NET 类之间的关系。

也许可以在运行时生成 Pascal 大小写的类,而无需预先定义它们,但我还没有足够深入地研究 JSON.NET 实现。也许是其他设置之一JsonSerializerSettings,例如传递 CustomCreationConverter,但我不确定详细信息。

  • .NET 中属性的大写约定是 PascalCase,而我从中获取 JSON 的系统的约定恰好是 CamelCase。我认为转换为 PascalCase 是最好的方法,但令我惊讶的是,你们中的许多人都反对它。 (2认同)