Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Json.Net: Serialize/Deserialize property as a value, not as an object

How can I achieve the following JSON representation of Id class when used in another class?

class Car
{
    public StringId Id { get; set; }
    public string Name { get; set; }
}

class StringId
{
    public string Value { get; set; }
}

// ---------------------------------------------

// Desired representation
{ "Id": "someId", "Name": "Ford" }

// Default (undesired) representation
{ "Id" : { "Value": "someId" }, "Name": "Ford" }
like image 682
Igor Soloydenko Avatar asked Nov 08 '16 06:11

Igor Soloydenko


People also ask

How does the JsonProperty attribute affect JSON serialization?

JsonPropertyAttribute indicates that a property should be serialized when member serialization is set to opt-in. It includes non-public properties in serialization and deserialization. It can be used to customize type name, reference, null, and default value handling for the property value.

What is the difference between serialize and deserialize JSON?

JSON is a format that encodes objects in a string. Serialization means to convert an object into that string, and deserialization is its inverse operation (convert string -> object). If you serialize this result it will generate a text with the structure and the record returned.

How do I deserialize an object in JSON?

A common way to deserialize JSON is to first create a class with properties and fields that represent one or more of the JSON properties. Then, to deserialize from a string or a file, call the JsonSerializer. Deserialize method.

How do you serialize and deserialize a string in C#?

SerializeObject<MatrixSerializable>(ms); Console. WriteLine("serialized: " + s); ms = Serializer. DeserializeObject<MatrixSerializable>(s); Console. WriteLine("deserialized: " + ms.


2 Answers

You could add a TypeConverter for StringId. Json.NET will pick up the type converter and use it to convert it from and to a string:

[TypeConverter(typeof(StringIdConverter))]
class StringId
{
    public string Value { get; set; }
}

class StringIdConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        if (sourceType == typeof(string))
            return true;
        return base.CanConvertFrom(context, sourceType);
    }

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        if (destinationType == typeof(StringId))
            return true;
        return base.CanConvertTo(context, destinationType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
    {
        if (value is string)
        {
            return new StringId { Value = (string)value };
        }
        return base.ConvertFrom(context, culture, value);
    }

    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType == typeof(string) && value is StringId)
        {
            return ((StringId)value).Value;
        }
        return base.ConvertTo(context, culture, value, destinationType);
    }
}

If your string representation contains embedded numeric or date/time data, be sure to convert that data using the culture passed in rather than the default, current culture. Json.NET will call the converter with the correct culture, which is the invariant culture by default, thus ensuring the generated JSON files are portable between cultures.

Sample fiddle.

Note however that, if you are using .Net Core, support for type converters was only added as of Json.NET 10.0.1. And support for type converters in Json.NET Portable builds is not available as of 10.0.3.

Alternatively, if you don't mind adding Json.NET-specific attributes to your type, you could use a custom JsonConverter:

[JsonConverter(typeof(StringIdConverter))]
class StringId
{
    public string Value { get; set; }
}

class StringIdConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(StringId);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var token = JToken.Load(reader);
        return new StringId { Value = (string)token };
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var id = (StringId)value;
        writer.WriteValue(id.Value);
    }
}

You can also set the converter in global settings.

Sample fiddle.

like image 192
dbc Avatar answered Oct 09 '22 07:10

dbc


You can override the ToString method of the StringId class to return the value

    public override string ToString()
    {
        return this.Value;
    }

You will need a TypeConverter later to deserialize from string to StringId

public class StringIdConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        if (sourceType == typeof(string))
        {
            return true;
        }
        return base.CanConvertFrom(context, sourceType);
    }


    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value is string)
        {
            return new StringId(value.ToString());
        }
        return base.ConvertFrom(context, culture, value);
    }
}

And decorate your StringId class with this attribute

[TypeConverter(typeof(StringIdConverter))]
public class StringId{
    ...
}
like image 29
Jek Avatar answered Oct 09 '22 08:10

Jek