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



我有一个这样的物理维度类:

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等值的枚举。

我的某些Physical属性的Dimension属性值取决于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值来确定重新创建Physical对象的Dimension

我的实际尝试是使用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值。

JsonConverter

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

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

  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

使用匿名对象:

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);

最新更新