Newtonsoft 使用交叉引用反序列化多个 JSON 文件

Newtonsoft deserialize multiple JSON files with cross references

我有两个 JSON 文件(我无法更改其格式),格式如下:

主文件 -

[
   {
      "Name":"XYZ",
      "UnitReferenceId":1
   },
   {
      "Name":"ABC",
      "UnitReferenceId":2
   }
]

lookup/referenceJSON文件-

[
   {
      "UnitReferenceId":1,
      "Units":[
         {
            "Unit":"mg",
            "Scale":1
         },
         {
            "Unit":"gm",
            "Scale":1000
         },
         {
            "Unit":"kg",
            "Scale":1000000
         }
      ]
   },
   {
      "UnitReferenceId":2,
      "Units":[
         {
            "Unit":"mm",
            "Scale":1
         },
         {
            "Unit":"m",
            "Scale":1000
         },
         {
            "Unit":"km",
            "Scale":1000000
         }
      ]
   }
]

我将如何使用 Newtonsoft JSON 将其反序列化为 C# 类 为:

public class Widget
{
    public string Name {get; set;}
    public UnitReference UnitReference { get; set; }
}

public class UnitReference
{
    public long UnitReferenceId { get; set; }
    public List<Unit> Units { get; set; }
}
public class Unit
{
    [JsonProperty("Unit")]
    public string UnitValue { get; set; }
    public long Scale { get; set; }
}

如有任何帮助,我们将不胜感激!

您可以按如下方式阅读您的两个 JSON 文件来完成此操作:

  1. 首先读取 UnitReference 的 lookup/reference JSON 文件作为 List<UnitReference>,然后转换为 Dictionary<long, UnitReference> 查找 table.

  2. 接下来,使用 Widgetcustom JsonConverter 读取主文件,该文件已通过 Dictionary<long, UnitReference> 查找 table 并且可以在 UnitReferenceIdUnitReference 在读写期间。

因此您的 classes 将如下所示:

public class UnitReference
{
    readonly long unitReferenceId;

    public UnitReference(long unitReferenceId)
    {
        this.unitReferenceId = unitReferenceId;
    }

    public long UnitReferenceId { get { return unitReferenceId; } }

    public List<Unit> Units { get; set; }
}

public class Unit
{
    [JsonProperty("Unit")]
    public string UnitValue { get; set; }
    public long Scale { get; set; }
}

public class Widget
{
    public string Name { get; set; }

    public UnitReference UnitReference { get; set; }
}

(我唯一的修改是将 UnitReferenceId 设置为只读,以便它可以安全地用作字典键。)

然后,定义以下转换器:

public class WidgetConverter : CustomPropertyConverterBase<Widget>
{
    readonly IDictionary<long, UnitReference> units;

    public WidgetConverter(IDictionary<long, UnitReference> units)
    {
        this.units = units;
    }

    protected override void ReadCustomProperties(JObject obj, Widget value, JsonSerializer serializer)
    {
        var id = (long?)obj.GetValue("UnitReferenceId", StringComparison.OrdinalIgnoreCase);
        if (id != null)
            value.UnitReference = units[id.Value];
    }

    protected override bool ShouldSerialize(JsonProperty property, object value)
    {
        if (property.UnderlyingName == nameof(Widget.UnitReference))
            return false;
        return base.ShouldSerialize(property, value);
    }

    protected override void WriteCustomProperties(JsonWriter writer, Widget value, JsonSerializer serializer)
    {
        if (value.UnitReference != null)
        {
            writer.WritePropertyName("UnitReferenceId");
            writer.WriteValue(value.UnitReference.UnitReferenceId);
        }
    }
}

public abstract class CustomPropertyConverterBase<T> : JsonConverter where T : class
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var jObj = JObject.Load(reader);
        var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
        var value = existingValue as T ?? (T)contract.DefaultCreator();

        ReadCustomProperties(jObj, value, serializer);

        // Populate the remaining properties.
        using (var subReader = jObj.CreateReader())
        {
            serializer.Populate(subReader, value);
        }

        return value;
    }

    protected abstract void ReadCustomProperties(JObject obj, T value, JsonSerializer serializer);

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
        writer.WriteStartObject();
        foreach (var property in contract.Properties.Where(p => ShouldSerialize(p, value)))
        {
            var propertyValue = property.ValueProvider.GetValue(value);
            if (propertyValue == null && serializer.NullValueHandling == NullValueHandling.Ignore)
                continue;
            writer.WritePropertyName(property.PropertyName);
            serializer.Serialize(writer, propertyValue);
        }
        WriteCustomProperties(writer, (T)value, serializer);
        writer.WriteEndObject();
    }

    protected virtual bool ShouldSerialize(JsonProperty property, object value)
    {
        return property.Readable && !property.Ignored && (property.ShouldSerialize == null || property.ShouldSerialize(value));
    }

    protected abstract void WriteCustomProperties(JsonWriter writer, T value, JsonSerializer serializer);
}

并反序列化如下:

var units = JsonConvert.DeserializeObject<List<UnitReference>>(unitsJsonString)
    .ToDictionary(u => u.UnitReferenceId);

var settings = new JsonSerializerSettings
{
    Converters = { new WidgetConverter(units) },
};
var widgets = JsonConvert.DeserializeObject<List<Widget>>(widgetsJsonString, settings);

备注:

  • 出于演示目的,我在这里从 JSON 字符串反序列化,但您可以直接从文件反序列化,如 Deserialize JSON from a file.

    所示
  • WidgetConverter 的基础 class CustomPropertyConverterBase<T> 自动读取和写入被(反)序列化对象的所有属性。 WidgetConverter 然后仅针对 UnitReference 属性 覆盖此行为,避免手动序列化 Widget.

  • 的所有剩余属性的必要性

示例 fiddle.

我用json2csharp for creating my classes quickly. If you have to implement it in code, see JSON C# Class Generator Project.