Reputation: 43
I have a physical dimension class like this:
public class Physical
{
public Dimension Dimension {get; set;}
public double Value {get; set;}
public string Unit {get; set;}
}
Dimension
is an enum with values like, Force
, Temperature
, Displacement
, Time
, etc.
and a class with Physical
properties like
public class MeasurementInfo
{
public Instrument Instrument {get; set;}
public Physical MaxReading {get; set;}
public Physical MinReading {get; set;}
public Physical AmbientTemperature {get; set;}
}
Instrument
is also an enum with values like Chronometer
, WeightScale
, Thermometer
, Caliper
, etc.
The Dimension
property value of some of my Physical
properties depends on the Instrument
value. Other ones are fixed. Example:
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"
}
};
What I want is to save this object as JSON like this:
{
"Instrument": "Weight Scale",
"Max Reading": "100 kg",
"Min Reading": "50 kg",
"AmbientTemperature": "27 °C"
}
Serialization is easy as we have Value
and Unit
defined. My problem is with deserialization because I have to read the Instrument
value to determine a Dimension
to recreate the Physical
objects.
My actual try is using a ContractResolver
. This way I can define my JsonConverter
based on the property type and name.
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;
}
}
}
}
The ??? is where I got stuck. The ContractResolver
doesn't work for instances so I can't know beforehand my Instrument
value.
Upvotes: 3
Views: 5494
Reputation: 129667
A JsonConverter
does not have access to the parent of the object that it is handling. So if the converter handles Physical
it won't be able to "see" the Instrument
inside MeasurementInfo
.
So there are two approaches I can see:
MeasurementInfo
in addition to Physical
. The converter will then be able to see the Instrument
and create Physical
as needed.Dimension
. For example, if the unit is kg
you know the dimension must be Weight
, regardless of the instrument, right? This would only break down if there are two units which look the same but represent different dimensions depending on the instrument. But based on your examples, I don't think this would be the case. This approach would be "cleaner" in my opinion.Here is an example of the first approach:
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);
}
}
Working round-trip demo: https://dotnetfiddle.net/ZUibQ1
For completeness, here is an example of the second approach:
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 },
};
}
Working round-trip demo: https://dotnetfiddle.net/1ecLNJ
Upvotes: 4
Reputation: 3574
Use anonymous object:
var objectToBeSerialized = new
{
Instrument = myMeasure.Instrument.ToString(),
MaxReading = $"{myMeasure.MaxReading.Value} {myMeasure.MaxReading.Unit}",
MinReading = $"{myMeasure.MinReading.Value} {myMeasure.MinReading.Unit}"
};
and convert this object into JSON using NewtonSoft.Json library:
var serializedJSON = JsonConvert.SerializeObject(objectToBeSerialized);
Upvotes: 0