Json.net при десериализации изменить тип и имя свойства - PullRequest
1 голос
/ 20 октября 2019

С помощью json.net, когда я десериализую строку json, мне нужно сопоставить список json с другим полем и преобразовать тип.

Чтобы было понятно, у меня есть этот класс (он сгенерировал код и могуне меняйте его):

[Serializable]
public partial class Nbgv
{
    public virtual IReadOnlyList<string> BuildMetadataWithCommitId => BuildMetadataWithCommitIdInternal.AsReadOnly();
    internal List<string> BuildMetadataWithCommitIdInternal { get; set; } = new List<string>();
}

У меня есть эта строка json:

string json = @"{
    ""BuildMetadataWithCommitId"": [
        ""c390a213b7""
    ]
}";

Как видите, BuildMetadataWithCommitId зависит от BuildMetadataWithCommitIdInternal.

Iпробовал несколько вещей с пользовательским преобразователем, но ничего не получалось. Я не нашел способа сообщить json.net, если вы нашли свойство, являющееся общим списком, сопоставьте его с полем, заканчивающимся * Internal.

private class CustomContractResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);
        property.Writable = true;
        if (IsList(property.PropertyType))
        {
            property.PropertyName += "Internal";
            property.PropertyType = ToList(property.PropertyType);
        }

        return property;
    }

    private Type ToList(Type t)
    {
        var genericListType = typeof(List<>);
        var specificListType = genericListType.MakeGenericType(t.GenericTypeArguments[0]);
        return specificListType;
    }

    private bool IsList(Type t)
    {
        if (t == null) return false;

        var res = t.IsGenericType &&
                   t.GetGenericTypeDefinition().IsAssignableFrom(typeof(IReadOnlyList<>));

        return res;
    }
}

1 Ответ

1 голос
/ 20 октября 2019

Ваша основная проблема заключается в том, что ваши CustomContractResolver только изменяют PropertyName и PropertyType возвращенных JsonProperty, однако основнойPropertyInfo, из которого он был создан, все еще принадлежит государственной суррогатной собственности, а не частной внутренней "реальной" собственности. Таким образом, ValueProvider, среди прочего, все равно будет неправильным.

Вместо этого вам нужно сгенерировать JsonProperty для внутреннего свойства, исправить его имя и доступностьи вернуть его вместо JsonProperty для государственной собственности. Это гарантирует, что сериализатор будет сериализовать и десериализовать внутреннее свойство вместо его общедоступного суррогата.

Следующий преобразователь контракта выполняет работу:

public class CustomContractResolver : DefaultContractResolver
{
    const string InternalSuffix = "Internal";

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = member as PropertyInfo;
        var jProperty = base.CreateProperty(member, memberSerialization);
        if (property != null && jProperty != null && memberSerialization != MemberSerialization.Fields && !jProperty.HasMemberAttribute)
        {
            var replacementName = jProperty.UnderlyingName + InternalSuffix;

            // Check for replacement property.
            var replacementProperty = jProperty.DeclaringType.GetProperty(replacementName, BindingFlags.Instance | BindingFlags.NonPublic);
            if (replacementProperty != null
                && replacementProperty.GetGetMethod(true) != null && replacementProperty.GetSetMethod(true) != null
                && ReplacementTypeCompatible(property, replacementProperty.PropertyType)
                )
            {
                var replacementJProperty = base.CreateProperty(replacementProperty, memberSerialization);
                replacementJProperty.PropertyName = jProperty.PropertyName;
                if (!replacementJProperty.Readable && replacementProperty.GetGetMethod(true) != null)
                    replacementJProperty.Readable = true;
                if (!replacementJProperty.Writable && replacementProperty.GetSetMethod(true) != null)
                    replacementJProperty.Writable = true;
                return replacementJProperty;
            }

            // Check for replacement field.
            var replacementField = jProperty.DeclaringType.GetField(replacementName, BindingFlags.Instance | BindingFlags.NonPublic);
            if (replacementField != null
                && ReplacementTypeCompatible(property, replacementField.FieldType)
                )
            {
                var replacementJProperty = base.CreateProperty(replacementField, memberSerialization);
                replacementJProperty.PropertyName = jProperty.PropertyName;
                replacementJProperty.Readable = true;
                replacementJProperty.Writable = true;
                return replacementJProperty;
            }
        }

        return jProperty;
    }

    static bool ReplacementTypeCompatible(PropertyInfo property, Type replacementType)
    {
        // Add here whatever restrictions you need
        if (property.PropertyType.IsGenericType && typeof(IReadOnlyList<>).IsAssignableFrom(property.PropertyType.GetGenericTypeDefinition())
            && replacementType.IsGenericType && typeof(List<>).IsAssignableFrom(replacementType.GetGenericTypeDefinition())
            && replacementType.GetGenericArguments().SequenceEqual(property.PropertyType.GetGenericArguments()))
            return true;
        return false;
    }
}

Чтобы использовать его, кэшируйте экземплярпреобразователь где-то для производительности :

static IContractResolver customContractResolver = new CustomContractResolver();

и десериализации следующим образом:

var settings = new JsonSerializerSettings
{
    ContractResolver = customContractResolver,
};
var root = JsonConvert.DeserializeObject<Nbgv>(json, settings);

Примечания:

  • Вна ваш вопрос вы указываете Мне нужно сопоставить список json с другим полем , однако в фактическом примере базовым элементом является свойство . Таким образом, в CreateProperty() я проверяю оба типа замены. Если в вашем производственном коде вам нужен только один или другой, вы можете удалить ненужную логику.

  • Проверка !jProperty.HasMemberAttribute предотвращает свойства, явно помеченные [JsonProperty] от замены. Это кажется правильным, но вы можете снять чек, если не хотите.

Демонстрационная скрипка здесь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...