简体   繁体   中英

C# JSON Custom Serialization of Class Including Attributes of Members/Properties

I have a number of classes that I need to serialize into a standard format like below:

Class Example:

public class MyClass
{
    [JsonProperty("prop1")]
    public string Prop1 { get; set; }
    [JsonProperty("prop2")]
    [CustomType("somevalue")]
    public string Prop2 { get; set; }
    //
    //
    [JsonProperty("propn")]
    [CustomType("anothervalue")]
    public string PropN { get; set; }
}

I need my JSON to look like this:

{
    "AllProps": [
        {
            "Key": "prop1",
            "Value": "value of prop1",
            "Type": "0" //default to "0" if CustomType attribute is null
        },
        {
            "Key": "prop2",
            "Value": "value of prop2",
            "Type": "somevalue"
        },
        {
            "Key": "propn",
            "Value": "value of propn",
            "Type": "anothervalue"
        }
    ]
}

How can I carry those "CustomType" attributes forward into my JObject/JTokens?

You can create a custom JsonConverter that serializes your MyClass in the required format by making use of the metadata stored in Json.NET's own JsonObjectContract to get a list of all serializable properties and their attributes.

First, define the following converter and attribute:

[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class CustomTypeAttribute : System.Attribute
{
    public CustomTypeAttribute(string type) => this.Type = type;
    public string Type { get; set; }
}

public class ObjectAsAllPropsConverter<TBase> : JsonConverter
{
    const string AllPropsName = "AllProps";
    const string KeyName = "Key";
    const string ValueName = "Value";
    const string TypeName = "Type";
    const string DefaultType = "0";

    static IContractResolver DefaultResolver { get; } = JsonSerializer.CreateDefault().ContractResolver;
    readonly IContractResolver resolver;

    public ObjectAsAllPropsConverter() : this(DefaultResolver) { }
    public ObjectAsAllPropsConverter(IContractResolver resolver) => this.resolver = resolver ?? throw new ArgumentNullException(nameof(resolver));

    public override bool CanConvert(Type objectType)
    {
        if (objectType.IsPrimitive || objectType == typeof(string) || !typeof(TBase).IsAssignableFrom(objectType))
            return false;
        return resolver.ResolveContract(objectType) is JsonObjectContract;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
        writer.WriteStartObject();
        writer.WritePropertyName(AllPropsName);
        writer.WriteStartArray();
        foreach (var property in contract.Properties.Where(p => ShouldSerialize(p, value)))
        {
            var propertyValue = property.ValueProvider.GetValue(value);
            if (propertyValue == null && (serializer.NullValueHandling == NullValueHandling.Ignore || property.NullValueHandling == NullValueHandling.Ignore))
                continue;
            writer.WriteStartObject();
            writer.WritePropertyName(KeyName);
            writer.WriteValue(property.PropertyName);
            writer.WritePropertyName(ValueName);
            if (propertyValue == null)
                writer.WriteNull();
            else if (property.Converter != null && property.Converter.CanWrite)
                property.Converter.WriteJson(writer, propertyValue, serializer);
            else
                serializer.Serialize(writer, propertyValue);
            writer.WritePropertyName(TypeName);
            var type = property.AttributeProvider.GetAttributes(typeof(CustomTypeAttribute), true).Cast<CustomTypeAttribute>().SingleOrDefault()?.Type ?? DefaultType;
            writer.WriteValue(type);
            writer.WriteEndObject();
        }
        writer.WriteEndArray();
        writer.WriteEndObject();
    }

    protected virtual bool ShouldSerialize(JsonProperty property, object value) =>
        property.Readable && !property.Ignored && (property.ShouldSerialize == null || property.ShouldSerialize(value));

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();
}

Now you can serialize to JSON as follows:

var myClass = new MyClass
{
    Prop1 = "value of prop1",
    Prop2 = "value of prop2",
    PropN = "value of propn",
};

var settings = new JsonSerializerSettings
{
    Converters = { new ObjectAsAllPropsConverter<object>() },
};
var json = JsonConvert.SerializeObject(myClass, Formatting.Indented, settings);

Which results in:

{
  "AllProps": [
    {
      "Key": "prop1",
      "Value": "value of prop1",
      "Type": "0"
    },
    {
      "Key": "prop2",
      "Value": "value of prop2",
      "Type": "somevalue"
    },
    {
      "Key": "propn",
      "Value": "value of propn",
      "Type": "anothervalue"
    }
  ]
}

Or if you need to serialize to an intermediate JObject for some reason, you may do:

var token = JObject.FromObject(myClass, JsonSerializer.CreateDefault(settings));

Notes:

  • I only implemented serialization as deserialization was not requested in the question.

  • The converter's CanConvert method automatically checks to see whether the incoming object type will be serialized as a JSON object. If you want all JSON objects to be serialized in the AllProps format, use ObjectAsAllPropsConverter<object> . If you only want a certain .NET type to be serialized in this format, restrict the generic constraint to this type, eg

    var settings = new JsonSerializerSettings { Converters = { new ObjectAsAllPropsConverter<MyClass>() }, };

Demo fiddle here .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM