JSON 使用 DataContract 反序列化变量命名参数

JSON deserialization of variable named parameter using 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 时不会获取它。

如果我更改 class 以支持 IExtensibleDataObject,我可以获得值部分但不是 属性 名称,这是一个问题。我希望在 deserialization/serialization 期间保持此值,以便能够根据 return 请求发送信息。我不打算改用 Json.NET 来解决这个问题,因为我想知道是否有可能以某种形式而不诉诸外部依赖。

有点难看,但事实证明您可以使用 IDataContractSurrogate 将变量名为 属性 的 class 反序列化为 Dictionary<string, object>,然后复制字典中的值到你的class。当然,您需要在 class 中添加另一个 属性 来保存 "special" 属性 的名称。

这是我能够开始工作的代理示例:

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;

另请注意,在您的 class 中,您不应使用 [DataMember] 属性标记 "special" 属性,因为它们在代理项中进行了特殊处理。

[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"}