使用类型属性

时间:2017-04-11 16:24:05

标签: c# interface json.net deserialization

首先 - 我知道我将在这个特定情况下解决我的问题,我将向您展示(通过保存命名空间,或者在反序列化期间通过自定义反序列化器,如果你理解我的意思),但是对于计划的需求不会出现面向未来的情况,所以请不要提出建议。

我很久以前就开始使用JSON.net了,这是我第一次尝试创建自定义转换器,我无法在网络上的任何地方找到它。我已经使用了几个为类似需求编码的转换器,所以如果我在这里有几个不需要的步骤 - 那么不仅要纠正问题本身,还要告诉我是否有什么可以删除为了性能优势,谢谢。

现在转向代码(我已经删除了所有不相关的东西)。 这是接口实现:

[JsonConverter(typeof(StringEnumConverter))]
public enum SomeInterfaceType
{
    NoType = 0,
    Type1 = 1,
    Type2 = 2,
    //...
}
[JsonConverter(typeof(SomeInterfaceConverter))]
public interface SomeInterface
{
    SomeInterfaceType StoredType { get; }
    //...
}

正如您所看到的,接口存储了一些与接口相关的一般信息,以及StoredType,稍后我们将在反序列化过程中使用它来识别特定的类。

课程本身非常直接:

public class Class1 : SomeInterface
{
    public SomeInterfaceType StoredType { get { return SomeInterfaceType.Type1; } }
   //...
}
public class Class2 : SomeInterface
{
    public SomeInterfaceType StoredType { get { return SomeInterfaceType.Type2; } }
   //...
}
//...

有很多这些,它们有很多属性和功能,但这与问题无关,所以这个例子就足够了。

这些类(接口)将存储在程序本身中:

public class LowerClass
{
    public Dictionary<SomeKey, List<SomeInterface>> interfaceDictionary;
    //...
}
public class MiddleClass
{
    private LowerClass ccc;
    //...
}
public class TopClass
{
    private MiddleClass sss;
    //...
}

实现该接口的类数量将随着时间的推移而增加,并且此选择的其他原因很少。 保存命名空间对于这种特殊情况非常适用,但序列化信息可以被各种程序(各种语言)使用,因此它不是最好的解决方案,甚至不提如果有人要手动更改(这应该是可能的),或者如果由于某种原因未来命名空间发生变化,正如我所说 - 不是最好的解决方案。

现在转向问题的核心:

 public class SomeInterfaceConverter: JsonConverter
 {
    public SomeInterfaceConverter() { }
    JsonObjectContract FindContract(JObject obj, JsonSerializer serializer)
    {
        SomeInterfaceType typeQ = obj["StoredType"].ToObject<SomeInterfaceType>();
        switch (typeQ)
        {
            case SomeInterfaceType.Type1:
                {
                    return serializer.ContractResolver.ResolveContract(typeof(Class1)) as JsonObjectContract;
                }
            case SomeInterfaceType.Type2:
                {
                    return serializer.ContractResolver.ResolveContract(typeof(Class2)) as JsonObjectContract;
                }
            //...
            case SomeInterfaceType.NoType:
            default:
                {
                    return null;
                }
        }
    }
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(SomeInterface);
    }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null) return null;
        JObject obj = JObject.Load(reader);
        var contract = FindContract(obj, serializer);
        if (contract == null) throw new JsonSerializationException("No contract found in " + obj.ToString());
        existingValue = contract.DefaultCreator();
        using (var sr = obj.CreateReader())
        {
            serializer.Populate(sr, existingValue);
        }
        return existingValue;
    }
    public override bool CanWrite { get { return false; } }
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

我不完全确定我是否需要使用这个合同的东西,但我基本上想要省略任何其他类的自定义转换器。此外,我想省略为整个主类做自定义转换器,并希望保留标准转换器,除了实现SomeInterface的类(此时它们只存在于特定的字典中,但是这可能在将来发生变化)同样)。

FindContract完全按预期执行,从json对象读取一切正常,然后可能返回所需的合同? (在网上找到)。 然后在这一行:

existingValue = contract.DefaultCreator();

它抛出NullReferenceException。合同肯定存在(如果它是正确生产的)。我玩了几个小时,试着在stackoverflow和其他网站上找到不同的解决方法,但无论我怎么做,我仍然坚持这个例外。

正如我从一开始就说过的那样 - 我对JSON.net很新,可能它应该很容易。也许我甚至不需要那个合同,而应该使用DeserializeObject<>或其他东西(我也看过那个,但找不到足够接近的东西,可以很容易地转移到我的案例中)。 ClassesX和TypesX的所有可能组合都是已知的,并且可以没有任何问题进行硬编码。

提前感谢您花时间和任何有价值的提示/更正。

更新

根据评论中的要求,这里是JSON:

{
  "LowerClass": {
    "interfaceDictionary": {
      "Key1": [
        {
          "StoredType": "Type1"
        },
        {
          "StoredType": "Type2"
        }
      ]
    }
  }
}

此外,我决定添加反序列化的方式,以防万一(因为上面的JSON省略了顶级类,而不是它与问题有任何关联):

MiddleClass whatever = JsonConvert.DeserializeObject<MiddleClass>(source);

1 个答案:

答案 0 :(得分:0)

我最终挖掘了JSON.net代码,并从我的理解 - 如果有任何方法迫使contract.DefaultCreator()按预期工作(不抛出NullReferenceException),那么它将是一个相当的复杂的事情要实现。

我没有试图弄清楚什么是“修复”DefaultCreator的最简单方法,而是简单地创建了一个变通办法并完全取代了它:

//replaced that:
existingValue = contract.DefaultCreator();
//with that:
existingValue = ContractToObject(type, contract);

功能本身:

object ContractToObject(SomeInterfaceType type, JsonObjectContract contract)
{
    switch (type)
    {
        case SomeInterfaceType.Type1:
            {
                return new Class1(Params);
            }
        case SomeInterfaceType.Type2:
            {
                return new Class2(Params);
            }
            //...
        case SomeInterfaceType.None:
        default:
            {
                return null;
            }
    }
}