Десериализация JSON именованного параметра переменной с использованием DataContract

Предположим, у нас есть объект JSON, похожий на:

{
  '12345': 'text string',
  'rel': 'myResource'
}

Построение DataContract для сопоставления с этим типом кажется довольно простым, например:

[DataContract]
MyResource
{
  [DataMember(Name = "12345")]
  public string SpecialValue { get; set; }

  [DataMember(Name = "rel")]
  public string Rel { get; set; }
}

Теперь возникает проблема, что имя свойства является переменным, поэтому не гарантируется, что оно будет «12345». Поскольку эта переменная не может быть правильно сопоставлена ​​с использованием атрибутов, она не будет получена при использовании DataContractJsonSerializer.

Если я изменю класс для поддержки IExtensibleDataObject, я смогу получить часть значения, но не имя свойства, что является проблемой. Я хочу сохранить это значение во время десериализации/сериализации, чтобы иметь возможность отправлять информацию по запросу на возврат. Я не собираюсь переходить на использование Json.NET для решения этой проблемы, поскольку хочу знать, возможно ли это в той или иной форме, не прибегая к внешней зависимости.


person ShelbyZ    schedule 08.10.2015    source источник
comment
Вы можете десериализовать в Dictionary<string, object> вместо строго типизированного класса. Будет ли это работать?   -  person Brian Rogers    schedule 08.10.2015
comment
Вы можете десериализовать в Dictionary<string, string>, но для этого вам нужно установить DataContractJsonSerializerSettings.UseSimpleDictionaryFormat=true, который доступен только в .Net 4.5 или более поздней версии.   -  person dbc    schedule 08.10.2015
comment
Предпочтение отдается строго типизированному классу. Интересно, смогу ли я использовать Data Contract Surrogate, чтобы суррогатный класс читал его как словарь и правильно устанавливал свойства других классов.   -  person ShelbyZ    schedule 09.10.2015
comment
Вам понадобится как минимум еще одно поле в вашем классе, скажем, public string SpecialName { get; set; }.   -  person dbc    schedule 09.10.2015


Ответы (1)


Это немного некрасиво, но оказывается, что вы можете использовать IDataContractSurrogate, чтобы десериализовать класс со свойством с переменным именем в Dictionary<string, object>, а затем скопировать значения из словаря в ваш класс. Конечно, вам нужно будет добавить еще одно свойство в свой класс, чтобы сохранить имя «специального» свойства.

Вот пример суррогата, который мне удалось заставить работать:

class MyDataContractSurrogate : IDataContractSurrogate
{
    public Type GetDataContractType(Type type)
    {
        if (type == typeof(MyResource))
        {
            return typeof(Dictionary<string, object>);
        }
        return type;
    }

    public object GetDeserializedObject(object obj, Type targetType)
    {
        if (obj.GetType() == typeof(Dictionary<string, object>) && 
            targetType == typeof(MyResource))
        {
            Dictionary<string, object> dict = (Dictionary<string, object>)obj;
            MyResource mr = new MyResource();
            foreach (PropertyInfo prop in GetInterestingProperties(typeof(MyResource)))
            {
                DataMemberAttribute att = prop.GetCustomAttribute<DataMemberAttribute>();

                object value;
                if (dict.TryGetValue(att.Name, out value))
                {
                    prop.SetValue(mr, value);
                    dict.Remove(att.Name);
                }
            }

            // should only be one property left in the dictionary
            if (dict.Count > 0)
            {
                var kvp = dict.First();
                mr.SpecialName = kvp.Key;
                mr.SpecialValue = (string)kvp.Value;
            }
            return mr;
        }
        return obj;
    }

    public object GetObjectToSerialize(object obj, Type targetType)
    {
        if (obj.GetType() == typeof(MyResource) && 
            targetType == typeof(Dictionary<string, object>))
        {
            MyResource mr = (MyResource)obj;
            Dictionary<string, object> dict = new Dictionary<string, object>();
            dict.Add(mr.SpecialName, mr.SpecialValue);
            foreach (PropertyInfo prop in GetInterestingProperties(typeof(MyResource)))
            {
                DataMemberAttribute att = prop.GetCustomAttribute<DataMemberAttribute>();
                dict.Add(att.Name, prop.GetValue(mr));
            }
            return dict;
        }
        return obj;
    }

    private IEnumerable<PropertyInfo> GetInterestingProperties(Type type)
    {
        return type.GetProperties().Where(p => p.CanRead && p.CanWrite &&
                       p.GetCustomAttribute<DataMemberAttribute>() != null);
    }

    // ------- The rest of these methods are not needed -------
    public object GetCustomDataToExport(Type clrType, Type dataContractType)
    {
        throw new NotImplementedException();
    }

    public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
    {
        throw new NotImplementedException();
    }

    public void GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes)
    {
        throw new NotImplementedException();
    }

    public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
    {
        throw new NotImplementedException();
    }

    public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
    {
        throw new NotImplementedException();
    }
}

Чтобы использовать суррогат, вам нужно создать экземпляр DataContractJsonSerializerSettings и передать его DataContractJsonSerializer со следующими наборами свойств. Обратите внимание: поскольку нам требуется параметр UseSimpleDictionaryFormat, это решение будет работать только с .Net 4.5 или более поздней версии.

var settings = new DataContractJsonSerializerSettings();
settings.DataContractSurrogate = new MyDataContractSurrogate();
settings.KnownTypes = new List<Type> { typeof(Dictionary<string, object>) };
settings.UseSimpleDictionaryFormat = true;

Также обратите внимание, что в вашем классе вы НЕ должны отмечать «особые» свойства атрибутом [DataMember], так как они обрабатываются особым образом в суррогате.

[DataContract]
class MyResource
{
    // Don't mark these with [DataMember]
    public string SpecialName { get; set; }
    public string SpecialValue { get; set; }

    [DataMember(Name = "rel")]
    public string Rel { get; set; }
}

Вот демо:

class Program
{
    static void Main(string[] args)
    {
        string json = @"
        {
            ""12345"": ""text string"",
            ""rel"": ""myResource""
        }";

        var settings = new DataContractJsonSerializerSettings();
        settings.DataContractSurrogate = new MyDataContractSurrogate();
        settings.KnownTypes = new List<Type> { typeof(Dictionary<string, object>) };
        settings.UseSimpleDictionaryFormat = true;

        MyResource mr = Deserialize<MyResource>(json, settings);

        Console.WriteLine("Special name: " + mr.SpecialName);
        Console.WriteLine("Special value: " + mr.SpecialValue);
        Console.WriteLine("Rel: " + mr.Rel);
        Console.WriteLine();

        json = Serialize(mr, settings);
        Console.WriteLine(json);
    }

    public static T Deserialize<T>(string json, DataContractJsonSerializerSettings settings = null)
    {
        using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(json)))
        {
            if (settings == null) settings = GetDefaultSerializerSettings();
            var ser = new DataContractJsonSerializer(typeof(T), settings);
            return (T)ser.ReadObject(ms);
        }
    }

    public static string Serialize(object obj, DataContractJsonSerializerSettings settings = null)
    {
        using (MemoryStream ms = new MemoryStream())
        {
            if (settings == null) settings = GetDefaultSerializerSettings();
            var ser = new DataContractJsonSerializer(obj.GetType(), settings);
            ser.WriteObject(ms, obj);
            return Encoding.UTF8.GetString(ms.ToArray());
        }
    }
}

Выход:

Special name: 12345
Special value: text string
Rel: myResource

{"12345":"text string","rel":"myResource"}
person Brian Rogers    schedule 08.10.2015
comment
Это то, о чем я подумал после того, как наткнулся на Словарь‹строка,строка›, как было предложено. Это ни в коем случае не элегантно, но это означает, что я могу поддерживать класс, как я и надеялся, спасибо! - person ShelbyZ; 09.10.2015
comment
@ShelbyZ Спасибо за редактирование - вы правы, это было глупо с моей стороны. - person Brian Rogers; 10.11.2016
comment
Наконец-то у меня появилась причина проверить это с каким-то другим кодом, и мне было интересно, почему значения словаря не удаляются. Можно с уверенностью сказать, что логика сработала так, как предполагалось. - person ShelbyZ; 10.11.2016