2

Is there any way to convert any of my custom attribute(s) into something when I call the JsonConvert.SerializeObject(...) function? For example, I have a class:

class A
{
  [UnitAttribute("---")]
  public double? Ratio { get; set; } 
}

When serialize any instance of such class, is there any way to put the value of the UnitAttribute into the Json string?

I found there is a IAttributeProvider interface in the API. But it seems the serialize function doesn't really use it.

Max
  • 175
  • 1
  • 12

1 Answers1

0

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.

dbc
  • 104,963
  • 20
  • 228
  • 340