Альтернативное имя свойства при десериализации - программирование
Подтвердить что ты не робот

Альтернативное имя свойства при десериализации

Относительно этого вопроса:

Как изменить имена свойств при сериализации с помощью Json.net?

Конечно, здорово, но могу ли я пирог и съесть его?

То, что я ищу - это приятный взгляд, имеет альтернативное имя для свойства таким образом, что строка может содержать.

Что-то вроде:

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

Оба

{
     "FooBar": "yup"
}

и

{     
      "foo_bar":"uhuh"
}

будет десериализоваться, как ожидалось.

Поскольку решение без атрибута будет работать или атрибут класса, например:

 [AllowCStylePropertyNameAlternatives]
4b9b3361

Ответ 1

Один из способов добиться этого - создать пользовательский 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. Как вы можете видеть, метод сначала пытается совместить точное имя, а затем пытается совпадение без учета регистра. Таким образом, мы уже имеем много-однозначное сопоставление между именами свойств JSON и класса.

Если вы измените этот метод, чтобы вычеркнуть все не-буквенно-цифровые символы из имени свойства до его соответствия, вы можете получить желаемое поведение без каких-либо специальных конвертеров или атрибутов. Вот модифицированный код:

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

Конечно, изменение исходного кода также имеет свои проблемы, но я подумал, что стоит упомянуть.

Ответ 2

Другим способом достижения этого является перехват процесса сериализации/десериализации рано, делая некоторые переопределения JsonReader и JsonWriter

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