基于另一个属性值的属性上的自定义JsonConverter

时间:2019-09-11 14:35:12

标签: c# json.net

我有一个这样的物理尺寸类:

public class Physical
{
  public Dimension Dimension {get; set;}
  public double Value {get; set;}
  public string Unit {get; set;}
}

Dimension是一个枚举,其值包含ForceTemperatureDisplacementTime等。

和一个具有Physical属性的类,例如

public class MeasurementInfo
{
  public Instrument Instrument {get; set;}
  public Physical MaxReading {get; set;}
  public Physical MinReading {get; set;}
  public Physical AmbientTemperature {get; set;}
}

Instrument也是一个具有ChronometerWeightScaleThermometerCaliper等值的枚举。

我的某些Dimension属性的Physical属性值取决于Instrument值。其他的是固定的。示例:

var myMeasure = new MeasurementInfo()
{
  Instrument = Instrument.WeightScale,
  MaxReading = new Physical()
  {
    Dimension = Dimension.Weight,
    Value = 100.0,
    Unit = "kg"
  },
  MinReading = new Physical()
  {
    Dimension = Dimension.Weight,
    Value = 50.0,
    Unit = "kg"
  },
  AmbientTemperature = new Physical()
  {
    Dimension = Dimension.Temperature,
    Value = 27,
    Unit = "°C"
  }
};

我想要的是将此对象另存为JSON:

{
  "Instrument": "Weight Scale",
  "Max Reading": "100 kg",
  "Min Reading": "50 kg",
  "AmbientTemperature": "27 °C"
}

序列化很容易,因为我们定义了ValueUnit。我的问题是反序列化,因为我必须读取Instrument值以确定一个Dimension才能重新创建Physical对象。

我的实际尝试是使用ContractResolver。这样,我可以根据属性类型和名称来定义我的JsonConverter

public class MyContractResolver : DefaultContractResolver
{
  protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
  {
    JsonProperty result = base.CreateProperty(member, memberSerialization);
    if(result.PropertyType == typeof(Physical))
    {
      var property = member as PropertyInfo;
      switch (result.PropertyName)
      {
        case "AmbientTemperature":
          result.Converter = new JsonPhysicalConverter(Dimension.Temperature);
          break;
        case "MaxReading":
        case "MinReading":
          result.Converter = new JsonPhysicalConverter(???);
          break;
      }
    }
  }
}

???是我卡住的地方。 ContractResolver在实例中不起作用,因此我无法事先知道我的Instrument值。

2 个答案:

答案 0 :(得分:1)

JsonConverter无权访问其正在处理的对象的父级。因此,如果转换器处理Physical,它将无法“看到” Instrument内部的MeasurementInfo

所以我可以看到两种方法:

  1. 让转换器处理MeasurementInfo之外的父Physical。然后,转换器将能够看到Instrument并根据需要创建Physical
  2. 使用单位值确定适当的Dimension。例如,如果单位为kg,则您知道尺寸必须为Weight,而与仪器无关,对吗?仅当有两个外观相同但代表不同尺寸的单元(取决于仪器)时,这种情况才会崩溃。但是根据您的示例,我认为情况并非如此。我认为这种方法会“更清洁”。

以下是第一种方法的示例:

public class MeasurementInfoConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(MeasurementInfo);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject obj = JObject.Load(reader);
        MeasurementInfo info = new MeasurementInfo();
        info.Instrument = obj["Instrument"].ToObject<Instrument>(serializer);
        info.MinReading = ReadPhysical(obj, "Min Reading", info.Instrument);
        info.MaxReading = ReadPhysical(obj, "Max Reading", info.Instrument);
        info.AmbientTemperature = ReadPhysical(obj, "Ambient Temperature", Instrument.Thermometer);
        return info;
    }

    private Physical ReadPhysical(JObject obj, string name, Instrument instrument)
    {
        Dimension dim = Dimension.Force;
        switch (instrument)
        {
            case Instrument.WeightScale: dim = Dimension.Weight; break;
            case Instrument.Chronometer: dim = Dimension.Time; break;
            case Instrument.Thermometer: dim = Dimension.Temperature; break;
            case Instrument.Caliper:     dim = Dimension.Displacement; break;
        }
        string[] parts = ((string)obj[name]).Split(new char[] { ' ' }, 2);
        Physical physical = new Physical()
        {
            Dimension = dim,
            Value = double.Parse(parts[0]),
            Unit = parts[1]
        };
        return physical;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        MeasurementInfo info = (MeasurementInfo)value;
        JObject obj = new JObject();
        obj.Add("Instrument", JToken.FromObject(info.Instrument, serializer));
        WritePhysical(obj, "Min Reading", info.MinReading);
        WritePhysical(obj, "Max Reading", info.MaxReading);
        WritePhysical(obj, "Ambient Temperature", info.AmbientTemperature);
        obj.WriteTo(writer);
    }

    private void WritePhysical(JObject obj, string name, Physical physical)
    {
        obj.Add(name, physical.Value.ToString("N0") + " " + physical.Unit);
    }
}

往返演示:https://dotnetfiddle.net/ZUibQ1


为完整起见,这是第二种方法的示例:

public class PhysicalConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Physical);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        string value = (string)reader.Value;
        string[] parts = value.Split(new char[] { ' ' }, 2);
        Dimension dim;
        if (!DimensionsByUnit.TryGetValue(parts[1], out dim)) dim = Dimension.Force;
        Physical physical = new Physical()
        {
            Dimension = dim,
            Value = double.Parse(parts[0]),
            Unit = parts[1]
        };
        return physical;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        Physical physical = (Physical)value;
        writer.WriteValue(physical.Value.ToString("N0") + " " + physical.Unit);
    }

    private static Dictionary<string, Dimension> DimensionsByUnit = new Dictionary<string, Dimension>
    {
        { "mg", Dimension.Weight },
        { "g", Dimension.Weight },
        { "kg", Dimension.Weight },
        { "°C", Dimension.Temperature },
        { "°F", Dimension.Temperature },
        { "°K", Dimension.Temperature },
        { "µs", Dimension.Time },
        { "ms", Dimension.Time },
        { "s", Dimension.Time },
        { "mm", Dimension.Displacement },
        { "cm", Dimension.Displacement },
        { "m", Dimension.Displacement },
    };
}

往返演示:https://dotnetfiddle.net/1ecLNJ

答案 1 :(得分:0)

使用匿名对象:

        var objectToBeSerialized = new
        {
            Instrument = myMeasure.Instrument.ToString(),
            MaxReading = $"{myMeasure.MaxReading.Value} {myMeasure.MaxReading.Unit}",
            MinReading = $"{myMeasure.MinReading.Value} {myMeasure.MinReading.Unit}"
        };

,然后使用NewtonSoft.Json库将此对象转换为JSON:

        var serializedJSON = JsonConvert.SerializeObject(objectToBeSerialized);