簡體   English   中英

使用ContractResolver在Newtonsoft的Json.NET中實現與MongoDB的BSON類映射等效的功能

[英]Using ContractResolver to realize an equivalent to MongoDB's BSON class map in Newtonsoft's Json.NET

這是一個后續問題這一個 我想要實現的是從我的類中替換[JsonConstructor] ,以便在我不進行任何序列化的項目中擺脫對Json.Net的依賴。 根據我從其他來源收集到的信息,使用ContractResolver是實現某種“為Foo類型使用委托Bar創建它的實例”映射的方法(請參閱MongoDB的類映射 )。 對我來說重要的是,我想堅持實體中的只讀字段,因此在創建過程中將給定參數分配給只讀字段時,必須使用一些帶有參數的工廠方法來創建實例。

我想出了以下解決方案(我從擁有一個公共的無參數構造函數,到一個有屬性的構造函數,再到一個私有的無參數的構造函數,再到一個沒有任何屬性的構造函數,在每一步中,我都探索了合同覆蓋方法中的行為。解析器):

class Thing
{
  public int Id { get; private set; }
  public string Name { get; private set; }

  //private Thing() { }

  //[JsonConstructor]
  private Thing(int id, string name)
  {
    this.Id = id;
    this.Name = name;
  }

  public static Thing Create(int id, string name)
  {
    return new Thing(id, name);
  }

  public static object Create2(object[] values)
  {
    return new Thing((int)values[0], (string)values[1]);
  }
}

我的合同解析器如下所示:

class CustomResolver : DefaultContractResolver
{
  public override JsonContract ResolveContract(Type type)
  {
    var b = base.ResolveContract(type);

    if(type == typeof(Thing))
    {
      var bb = b as JsonObjectContract;

      // figured out the name of the backing field using ILSpy
      var prop = typeof(JsonObjectContract).GetField("_creatorParameters",
        System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

      // in order to set the value of property CreatorParameters
      // which is used as parameter when OverrideCreator is invoked
      prop.SetValue(bb, bb.Properties); 

      bb.OverrideCreator = new ObjectConstructor<object>(Thing.Create2);
    }

    return b;
  }
}

這是它的用法:

Thing t1 = Thing.Create("Flim", 1);

// Serializer settings
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ContractResolver = new CustomResolver();
settings.PreserveReferencesHandling = PreserveReferencesHandling.None;
settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
settings.Formatting = Newtonsoft.Json.Formatting.Indented;

string json = JsonConvert.SerializeObject(t1, settings);
Thing t2 = JsonConvert.DeserializeObject<Thing>(json, settings);

它可以按我預期的方式工作,但是由於我通過使用反射對系統進行了入侵,因此我確信自己所做的事情完全錯誤,並且必須有一種更清潔的方法。 有任何想法嗎?

編輯

直到現在我還沒有想到的是,我有一些歸因於[JsonConverter(typeof(SomeSubstituteType))] ,如果我想完全消除這種依賴性,我也將擺脫掉。 到目前為止,這些類尚未包括在合同解析器類的類型檢查中。 是否有一種優雅的方法可以使它在沒有屬性的情況下工作,而無需通過在到目前為止配置的JsonSerializerSettings之外另外配置JsonSerializerSettings的方式在解析器中添加其他檢查呢?

的確, DefaultContractResolver當前無法輕松注入邏輯以選擇自定義構造函數,因為:

不過,您可以對代碼進行以下改進:

  1. 覆蓋CreateObjectContract而不是ResolveContract 前者在第一次遇到給定類型類型的對象以構造該類型的契約時被稱為第一次,后者隨后被緩存。 會為遇到的每個對象(包括基元)調用后者。 因此,覆蓋前者將具有更高的性能。 另外, CreateObjectContract()處於線程安全鎖內,而ResolveContract()不在,可以同時從多個線程中調用。 因此,您當前的代碼可能不是線程安全的。

  2. JsonObjectContract.CreatorParameters是一個可變集合,因此您只需清除內容並添加新屬性即可。

因此,您的合同解析器可能如下所示:

class CustomResolver : DefaultContractResolver
{
    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        var contract = base.CreateObjectContract(objectType);

        if (objectType == typeof(Thing))
        {
            contract.CreatorParameters.Clear();
            // For safety, specify the order concretely.
            contract.CreatorParameters.AddProperty(contract.Properties["Id"]); // Use nameof() in latest version.
            contract.CreatorParameters.AddProperty(contract.Properties["Name"]); // Use nameof() in latest version.
            contract.OverrideCreator = new ObjectConstructor<object>(Thing.Create2);
        }

        return contract;
    }
}

小提琴的例子。

更新

我有一些歸因於[JsonConverter(typeof(SomeSubstituteType))] ,如果我想完全消除視差,也可以擺脫……是否有一種優雅的方法可以使它在沒有屬性且不添加的情況下工作通過某種方式將JsonSerializerSettings配置為到目前為止配置的內容,從而在解析器中進行其他檢查?

是的,請確保為您的轉換器實現了JsonConverter.CanConvert() ,然后將它們添加到JsonSerializerSettings.Converters

public class SomeSubstituteType : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(SomeOriginalType).IsAssignableFrom(objectType);
    }

    // Remainder as before
}

然后執行:

var settings = new JsonSerializerSettings
{
    Converters = { new SomeSubstituteType(), new SecondConverter(), ... },
};

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM