The simplest thing to do would be to create a JsonConverter
that adds the attribute text (which I assume corresponds to units, in this case) and attach the converter to the property:
class A
{
[JsonConverter(typeof(UnitConverter), new object [] { "mm" })]
public double? Ratio { get; set; }
}
public class UnitConverter : JsonConverter
{
public string Units { get; set; }
string UnitsPostfix { get { return string.IsNullOrEmpty(Units) ? string.Empty : " " + Units; } }
public UnitConverter(string units)
{
this.Units = units;
}
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException(); // Not called when applied directly to a property.
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jvalue = JValue.Load(reader);
if (jvalue.Type == JTokenType.String)
{
var s = (string)jvalue;
if (s.EndsWith(Units))
jvalue = (JValue)s.Substring(0, s.LastIndexOf(Units)).Trim();
}
return jvalue.ToObject(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var jvalue = JValue.FromObject(value);
if (jvalue.Type == JTokenType.Null)
jvalue.WriteTo(writer);
else
writer.WriteValue((string)jvalue + UnitsPostfix);
}
}
Notice I can pass the unit string directly to the converter's constructor in the attribute declaration.
If you have lots of fields and properties in your code base with UnitAttribute
and want to apply the converter to all of them automatically, you could create a custom IContractResolver
derived from an existing contract resolver such as DefaultContractResolver
that applies the necessary converter:
public class UnitContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.Converter == null && property.MemberConverter == null)
{
var attr = property.AttributeProvider.GetAttributes(typeof(UnitAttribute), true).Cast<UnitAttribute>().Where(a => !string.IsNullOrEmpty(a.Name)).FirstOrDefault();
if (attr != null)
{
property.Converter = property.MemberConverter = new UnitConverter(attr.Name);
}
}
return property;
}
}
You can either use the the contract resolver explicitly, like so:
var settings = new JsonSerializerSettings() { ContractResolver = new UnitContractResolver() };
var json = JsonConvert.SerializeObject(a, settings);
Debug.WriteLine(json);
var a11 = JsonConvert.DeserializeObject<A>(json, settings);
Debug.Assert(a.Ratio == a.Ratio);
Or set it in the Json.net global settings for automatic use.