使用JSON格式时如何处理继承的类?

时间:2018-07-26 12:56:28

标签: c# json asp.net-web-api

我需要Web API返回以json格式序列化的Rule个实例的列表。

[HttpGet]
[SwaggerOperation(nameof(GetRules))]
[SwaggerResponse(StatusCodes.Status200OK, typeof(List<Rule>), "Rules")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> GetRules()
{
    List<Rule> rules = /* retrieve rule from some storage */;

    return Ok(rules);
}

目前,有两种规则,每种规则在Rule类中共享的规则之上都有特定的属性。一个规则称为RuleWithExpiration,另一个规则称为RuleWithGracePeriod

[JsonObject(MemberSerialization.OptIn)]
public class Rule
{
    [JsonProperty("id")]
    public Guid Id { get; }

    [JsonProperty("name")]
    public string Name { get; }

    [JsonConstructor]
    public Rule(Guid id, string name)
    {
        Id = id;
        Name = name;
    }
}

[JsonObject(MemberSerialization.OptIn)]
public class RuleWithExpiration : Rule
{
    [JsonProperty("someInfo")]
    public string SomeInfo { get; }

    [JsonProperty("expiration")]
    DateTime Expiration { get; }

    [JsonConstructor]
    public RuleWithExpiration(Guid id, string name, string someInfo, DateTime expiration) : base(id, name)
    {
        SomeInfo = someInfo;
        Expiration = expiration;
    }
}

[JsonObject(MemberSerialization.OptIn)]
public class RuleWithGracePeriod : Rule
{
    [JsonProperty("gracePeriod")]
    public TimeSpan GracePeriod { get; }

    [JsonConstructor]
    public RuleWithGracePeriod(Guid id, string name, TimeSpan gracePeriod) : base(id, name)
    {
        GracePeriod = gracePeriod;
    }
}

我遇到的问题是,当我尝试反序列化该类层次结构时,会遇到问题。反序列化之后,由于没有要求序列化程序将类型信息包含为it is considered a security issue,所以我得到了Rule实例列表。

void Main()
{
    List<Rule> rules = new List<Rule>
    {
        new RuleWithExpiration(Guid.NewGuid(), "Rule with expiration", "Wat?", DateTime.UtcNow.AddHours(1d)),
        new RuleWithGracePeriod(Guid.NewGuid(), "Rule with grace period", TimeSpan.FromHours(1d)),
    };

    var serializedRule = JsonConvert.SerializeObject(rules);

    serializedRule.Dump();

    List<Rule> deserializedRule = JsonConvert.DeserializeObject<List<Rule>>(serializedRule);

    deserializedRule.Dump();
}

这是序列化的字符串:

[{"someInfo":"Wat?","expiration":"2018-07-26T13:32:06.2287669Z","id":"29fa0603-c103-4a95-b627-0097619a7645","name":"Rule with expiration"},{"gracePeriod":"01:00:00","id":"bd8777bb-c6b3-4172-916a-546775062eb1","name":"Rule with grace period"}]

这是我对其反序列化后得到的Rule个实例的列表(如LINQPad中所示):

Deserialized instances

问题

是否可以在此上下文中保留此继承树,还是必须以某种方式重新排列这些类?如果是这样,怎么办呢?

解决方案

我还没有找到感觉良好的解决方案。

例如,我可以吃一些  RuleAggregate类就像这样,但是每次我引入一种新规则时,我都必须编辑该类并处理影响:

[JsonObject(MemberSerialization.OptIn)]
public class RuleAggregate
{
    [JsonProperty("expirations")]
    public List<RuleWithExpiration> Expirations {get;}

    [JsonProperty("gracePeriods")]
    public List<RuleWithGracePeriod> GracePeriods {get;}

    [JsonConstructor]
    public RuleAggregate(List<RuleWithExpiration> expirations, List<RuleWithGracePeriod> gracePeriods)
    {
        Expirations = expirations;
        GracePeriods = gracePeriods;
    }
}

如果我想保留继承树,那么我在权衡较小的情况下找到的解决方案就是依靠良好的XML序列化。

1 个答案:

答案 0 :(得分:1)

好的,正确的TypeNameHandling.All使其容易受到攻击。那这种方法呢?

void Main()
{
    Stockholder stockholder = new Stockholder
    {
        FullName = "Steve Stockholder",
        Businesses = new List<Business>
        {
            new Hotel
            {
                Name = "Hudson Hotel",
                Stars = 4
            }
        }

    };

    var settings = new JsonSerializerSettings
    {
        TypeNameHandling = TypeNameHandling.Objects,
        SerializationBinder = new KnownTypesBinder { KnownTypes = new List<Type> { typeof(Stockholder), typeof(Hotel) }}
    };

    string ok;

    /*
    ok = JsonConvert.SerializeObject(stockholder, Newtonsoft.Json.Formatting.Indented, settings);

    Console.WriteLine(ok);*/

    ok = @"{
  ""$type"": ""Stockholder"",
  ""FullName"": ""Steve Stockholder"",
  ""Businesses"": [
    {
      ""$type"": ""Hotel"",
      ""Stars"": 4,
      ""Name"": ""Hudson Hotel""
    }
  ]
}";

    JsonConvert.DeserializeObject<Stockholder>(ok, settings).Dump();

    var vector = @"{
  ""$type"": ""Stockholder"",
  ""FullName"": ""Steve Stockholder"",
  ""Businesses"": [
    {
      ""$type"": ""System.IO.FileInfo, System.IO.FileSystem"",
      ""fileName"": ""d:\rce-test.txt"",
      ""IsReadOnly"": true
    }
  ]
}";

    JsonConvert.DeserializeObject<Stockholder>(vector, settings).Dump(); // will fail
}

public class KnownTypesBinder : ISerializationBinder
{
    public IList<Type> KnownTypes { get; set; }

    public Type BindToType(string assemblyName, string typeName)
    {
        return KnownTypes.SingleOrDefault(t => t.Name == typeName);
    }

    public void BindToName(Type serializedType, out string assemblyName, out string typeName)
    {
        assemblyName = null;
        typeName = serializedType.Name;
    }
}

public abstract class Business
{
    public string Name { get; set; }
}

public class Hotel: Business
{
    public int Stars { get; set; }
}

public class Stockholder
{
    public string FullName { get; set; }
    public IList<Business> Businesses { get; set; }
}