21

关于这个问题:

使用 Json.net 序列化时如何更改属性名称?

当然,很好,但是我可以吃蛋糕吗?

我正在寻找的是一种令人赏心悦目的方式,可以为属性提供替代名称,以便字符串可以包含其中任何一个。

就像是:

[BetterJsonProperty(PropertyName = "foo_bar")]
public string FooBar { get; set; }

两个都

{
     "FooBar": "yup"
}

{     
      "foo_bar":"uhuh"
}

将按预期反序列化。

由于没有属性的解决方案将起作用或类上的属性,例如:

 [AllowCStylePropertyNameAlternatives]
4

2 回答 2

30

实现此目的的一种方法是创建自定义JsonConverter. 这个想法是让转换器枚举我们感兴趣的对象的 JSON 属性名称,从名称中去除非字母数字字符,然后尝试通过反射将它们与实际的对象属性匹配。以下是它在代码中的样子:

public class LaxPropertyNameMatchingConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType.IsClass;
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        object instance = objectType.GetConstructor(Type.EmptyTypes).Invoke(null);
        PropertyInfo[] props = objectType.GetProperties();

        JObject jo = JObject.Load(reader);
        foreach (JProperty jp in jo.Properties())
        {
            string name = Regex.Replace(jp.Name, "[^A-Za-z0-9]+", "");

            PropertyInfo prop = props.FirstOrDefault(pi => 
                pi.CanWrite && string.Equals(pi.Name, name, StringComparison.OrdinalIgnoreCase));

            if (prop != null)
                prop.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
        }

        return instance;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

要将自定义转换器与特定类一起使用,您可以使用[JsonConverter]如下属性装饰该类:

[JsonConverter(typeof(LaxPropertyNameMatchingConverter))]
public class MyClass
{
    public string MyProperty { get; set; }
    public string MyOtherProperty { get; set; }
}

这是转换器的简单演示:

class Program
{
    static void Main(string[] args)
    {
        string json = @"
        [
            { 
                ""my property"" : ""foo"",
                ""my-other-property"" : ""bar"",
            },
            { 
                ""(myProperty)"" : ""baz"",
                ""myOtherProperty"" : ""quux""
            },
            { 
                ""MyProperty"" : ""fizz"",
                ""MY_OTHER_PROPERTY"" : ""bang""
            }
        ]";

        List<MyClass> list = JsonConvert.DeserializeObject<List<MyClass>>(json);

        foreach (MyClass mc in list)
        {
            Console.WriteLine(mc.MyProperty);
            Console.WriteLine(mc.MyOtherProperty);
        }
    }
}

输出:

foo
bar
baz
quux
fizz
bang

虽然这个解决方案在大多数情况下应该可以完成这项工作,但如果您同意直接更改 Json.Net 源代码的想法,还有一个更简单的解决方案。事实证明,你可以通过向类中添加一行代码来完成同样的事情Newtonsoft.Json.Serialization.JsonPropertyCollection。在这个类中,有一个被调用的方法GetClosestMatchProperty(),如下所示:

public JsonProperty GetClosestMatchProperty(string propertyName)
{
    JsonProperty property = GetProperty(propertyName, StringComparison.Ordinal);
    if (property == null)
        property = GetProperty(propertyName, StringComparison.OrdinalIgnoreCase);

    return property;
}

在反序列化程序调用此方法时,JsonPropertyCollection包含正在反序列化的类的所有属性,propertyName参数包含要匹配的 JSON 属性名称的名称。如您所见,该方法首先尝试完全匹配名称,然后尝试不区分大小写的匹配。因此,我们已经在 J​​SON 和类属性名称之间进行了多对一映射。

如果您修改此方法以在匹配之前从属性名称中去除所有非字母数字字符,那么您可以获得所需的行为,而无需任何特殊的转换器或属性。这是修改后的代码:

public JsonProperty GetClosestMatchProperty(string propertyName)
{
    propertyName = Regex.Replace(propertyName, "[^A-Za-z0-9]+", "");
    JsonProperty property = GetProperty(propertyName, StringComparison.Ordinal);
    if (property == null)
        property = GetProperty(propertyName, StringComparison.OrdinalIgnoreCase);

    return property;
}

当然,修改源代码也有问题,但我认为值得一提。

于 2013-11-10T03:22:00.627 回答
9

实现这一点的另一种方法是尽早拦截序列化/反序列化过程,通过执行一些覆盖JsonReaderJsonWriter

public class CustomJsonWriter : JsonTextWriter
{
    private readonly Dictionary<string, string> _backwardMappings;

    public CustomJsonWriter(TextWriter writer, Dictionary<string, string> backwardMappings)
        : base(writer)
    {
        _backwardMappings = backwardMappings;
    }

    public override void WritePropertyName(string name)
    {
        base.WritePropertyName(_backwardMappings[name]);
    }
}

public class CustomJsonReader : JsonTextReader
{
    private readonly Dictionary<string, string> _forwardMappings;


    public CustomJsonReader(TextReader reader, Dictionary<string, string> forwardMappings )
        : base(reader)
    {
        _forwardMappings = forwardMappings;
    }

    public override object Value
    {
        get
        {
            if (TokenType != JsonToken.PropertyName)
                return base.Value;

            return _forwardMappings[base.Value.ToString()];
        }
    }
}

完成此操作后,您可以通过执行序列化

var mappings = new Dictionary<string, string>
{
    {"Property1", "Equivalent1"},
    {"Property2", "Equivalent2"},
};
var builder = new StringBuilder();
JsonSerializer.Create().Serialize(new CustomJsonWriter(new StringWriter(builder), mappings), your_object);

并通过做反序列化

var mappings = new Dictionary<string, string>
{
    {"Equivalent1", "Property1"},
    {"Equivalent2", "Property2"},
};
var txtReader = new CustomJsonReader(new StringReader(jsonString), mappings);
var your_object = JsonSerializer.Create().Deserialize<Your_Type>(txtReader);
于 2014-08-06T12:49:10.747 回答