JSON.Net: Serialize/Deserialize Property as a Value, Not as an Object

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

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.

JSON.NET deserialize JSON object stored as property

You need a custom JsonConverter

class StringTypeConverter : Newtonsoft.Json.JsonConverter
{
public override bool CanRead => true;
public override bool CanWrite => true;

public override bool CanConvert(Type objectType)
{
return true;
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
string json = (string)reader.Value;
var result = JsonConvert.DeserializeObject(json, objectType);
return result;
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var json = JsonConvert.SerializeObject(value);
serializer.Serialize(writer, json);
}
}

and add a JsonConverterAttribute to the InnerMessage property

public class Envelope
{
public string Type { get; set; }
[Newtonsoft.Json.JsonConverter(typeof(StringTypeConverter))]
public Message InnerMessage { get; set; }
}

public class Message
{
public string From { get; set; }
public string To { get; set; }
public string Body { get; set; }
}

and now you can serialize/deserialize the Envelope class with

var envelope = JsonConvert.DeserializeObject<Envelope>( jsonMessage );

How to set the value while deserialization if property not included in json string using custom JsonConverter

You can create a custom converter where you let Newtonsoft read and parse all the properties that are present in the json and you can manually assign values to the missing ones:

public class TestClassConverter : JsonConverter<TestClass>
{
private static readonly Random rand = new Random();
public override TestClass ReadJson(JsonReader reader, Type objectType, TestClass existingValue, bool hasExistingValue, JsonSerializer serializer)
{
//Let Newtonsoft do the heavy lifting
var jObject = JObject.Load(reader);
var target = new TestClass();
serializer.Populate(jObject.CreateReader(), target);

//Set the intact property manually
target.InternalID = rand.Next();
return target;
}

public override void WriteJson(JsonWriter writer, TestClass value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

I've used random, but you can assign whatever value you want.

Usage:

var data = JsonConvert.DeserializeObject<List<TestClass>>(json, converters: new TestClassConverter());

With this approach you don't have to decorate TestClass's InternalID property with a JsonConverterAttribute.


UPDATE: Non-generic JsonConverter version

public class TestClassConverter : JsonConverter
{
private static readonly Random rand = new Random();
public override bool CanConvert(Type objectType)
{
return objectType == typeof(TestClass);
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var target = new TestClass();
serializer.Populate(jObject.CreateReader(), target);

target.InternalID = rand.Next();
return target;
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

Json.NET does not deserialize property with custom getter and immutable type

The culprit is this line in your ClassWithCustomProperty class:

    get => value = (value ?? new ImmutableValue(0));

Json.Net's default behavior is to reuse existing objects rather than replace them. So when it is time to deserialize the ImmutableValue on ClassWithCustomProperty, the serializer first checks whether there is an existing value. The accessor (above) finds that there isn't, so it creates one with value 0, which is returned to the serializer. The serializer then attempts to reuse this existing object, but since it is readonly, there is no way to set the value 42 on it. So the value remains zero.

There is an ObjectCreationHandling setting which can be used to change this behavior. If you set it to Replace, it will work the way you want. Try like this:

var settings = new JsonSerializerSettings 
{
ObjectCreationHandling = ObjectCreationHandling.Replace
};
var obj = JsonConvert.DeserializeObject<ClassWithCustomProperty>(json, settings);

Fiddle: https://dotnetfiddle.net/UVDMsL

How to deserialize a property on JSON from one type but serialize to another type with the same name?

Thanks to @Fildor for leading me in the right direction.
The answer was deceptively simple, add a type converter and decorate the new property with the converter.

public class Something {
[JsonConverter(typeof(FeeConverter))]
public Fee Fee { get; set;}
}

public class Fee {
public decimal? Amount { get; set; }
public int Type { get; set; }
}

public class FeeConverter : JsonConverter
{

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Float || reader.TokenType == JsonToken.Integer)
{
var property = JValue.Load(reader);
return new Fee() { Amount = property.Value<decimal>() };
}

return serializer.Deserialize(reader, objectType);
}

public override bool CanWrite => false;

public override bool CanConvert(Type objectType) => false;
}

How can I make Json.NET serialize and deserialize declared properties of custom dynamic types that also implement IDictionary string, object ?

You have a few problems here:

  1. You need to correctly override DynamicObject.GetDynamicMemberNames() as explained in this answer to Serialize instance of a class deriving from DynamicObject class by AlbertK
    for Json.NET to be able to serialize your dynamic properties.

    (This has already been fixed in the edited version of your question.)

  2. Declared properties do not show up unless you explicitly mark them with [JsonProperty] (as explained in this answer to C# How to serialize (JSON, XML) normal properties on a class that inherits from DynamicObject) but your type definitions are read-only and cannot be modified.

    The problem here seems to be that JsonSerializerInternalWriter.SerializeDynamic() only serializes declared properties for which JsonProperty.HasMemberAttribute == true. (I don't know why this check is made there, it would seem to make more sense to set CanRead or Ignored inside the contract resolver.)

  3. You would like for your class to implement IDictionary<string, object>, but if you do, it breaks deserialization; declared properties are no longer populated, but are instead added to the dictionary.

    The problem here seems to be that DefaultContractResolver.CreateContract() returns JsonDictionaryContract rather than JsonDynamicContract when the incoming type implements IDictionary<TKey, TValue> for any TKey and TValue.

Assuming you have fixed issue #1, issues #2 and #3 can be handled by using a custom contract resolver such as the following:

public class MyContractResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
// Prefer JsonDynamicContract for MyDynamicObject
if (typeof(MyDynamicObject).IsAssignableFrom(objectType))
{
return CreateDynamicContract(objectType);
}
return base.CreateContract(objectType);
}

protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var properties = base.CreateProperties(type, memberSerialization);
// If object type is a subclass of MyDynamicObject and the property is declared
// in a subclass of MyDynamicObject, assume it is marked with JsonProperty
// (unless it is explicitly ignored). By checking IsSubclassOf we ensure that
// "bookkeeping" properties like Count, Keys and Values are not serialized.
if (type.IsSubclassOf(typeof(MyDynamicObject)) && memberSerialization == MemberSerialization.OptOut)
{
foreach (var property in properties)
{
if (!property.Ignored && property.DeclaringType.IsSubclassOf(typeof(MyDynamicObject)))
{
property.HasMemberAttribute = true;
}
}
}
return properties;
}
}

Then, to use the contract resolver, cache it somewhere for performance:

static IContractResolver resolver = new MyContractResolver();

And then do:

var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
};
string json = JsonConvert.SerializeObject(obj, settings);

Sample fiddle here.

JSON.net (de)serialize untyped property

If you serialize your class with TypeNameHandling.All or TypeNameHandling.Auto,
then when the UntypedProperty property would be serialized as a JSON container (either an object or array) Json.NET should correctly serialize and deserialize it by storing type information in the JSON file in a "$type" property. However, in cases where UntypedProperty is serialized as a JSON primitive (a string, number, or Boolean) this doesn't work because, as you have noted, a JSON primitive has no opportunity to include a "$type" property.

The solution is, when serializing a type with a property of type object, to serialize wrappers classes for primitive values that can encapsulate the type information, along the lines of this answer. Here is a custom JSON converter that injects such a wrapper:

public class UntypedToTypedValueConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException("This converter should only be applied directly via ItemConverterType, not added to JsonSerializer.Converters");
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var value = serializer.Deserialize(reader, objectType);
if (value is TypeWrapper)
{
return ((TypeWrapper)value).ObjectValue;
}
return value;
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (serializer.TypeNameHandling == TypeNameHandling.None)
{
Console.WriteLine("ObjectItemConverter used when serializer.TypeNameHandling == TypeNameHandling.None");
serializer.Serialize(writer, value);
}
// Handle a couple of simple primitive cases where a type wrapper is not needed
else if (value is string)
{
writer.WriteValue((string)value);
}
else if (value is bool)
{
writer.WriteValue((bool)value);
}
else
{
var contract = serializer.ContractResolver.ResolveContract(value.GetType());
if (contract is JsonPrimitiveContract)
{
var wrapper = TypeWrapper.CreateWrapper(value);
serializer.Serialize(writer, wrapper, typeof(object));
}
else
{
serializer.Serialize(writer, value);
}
}
}
}

abstract class TypeWrapper
{
protected TypeWrapper() { }

[JsonIgnore]
public abstract object ObjectValue { get; }

public static TypeWrapper CreateWrapper<T>(T value)
{
if (value == null)
return new TypeWrapper<T>();
var type = value.GetType();
if (type == typeof(T))
return new TypeWrapper<T>(value);
// Return actual type of subclass
return (TypeWrapper)Activator.CreateInstance(typeof(TypeWrapper<>).MakeGenericType(type), value);
}
}

sealed class TypeWrapper<T> : TypeWrapper
{
public TypeWrapper() : base() { }

public TypeWrapper(T value)
: base()
{
this.Value = value;
}

public override object ObjectValue { get { return Value; } }

public T Value { get; set; }
}

Then apply it to your type using [JsonConverter(typeof(UntypedToTypedValueConverter))]:

public class Example
{
public int TypedProperty { get; set; }
[JsonConverter(typeof(UntypedToTypedValueConverter))]
public object UntypedProperty { get; set; }
}

If you cannot modify the Example class in any way to add this attribute (your comment The class isn't mine to change suggests as much) you could inject the converter with a custom contract resolver:

public class UntypedToTypedPropertyContractResolver : DefaultContractResolver
{
readonly UntypedToTypedValueConverter converter = new UntypedToTypedValueConverter();

// As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
// http://www.newtonsoft.com/json/help/html/ContractResolver.htm
// http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
// "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
// See also https://stackoverflow.com/questions/33557737/does-json-net-cache-types-serialization-information
static UntypedToTypedPropertyContractResolver instance;

// Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
static UntypedToTypedPropertyContractResolver() { instance = new UntypedToTypedPropertyContractResolver(); }

public static UntypedToTypedPropertyContractResolver Instance { get { return instance; } }

protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);

foreach (var property in contract.Properties.Concat(contract.CreatorParameters))
{
if (property.PropertyType == typeof(object)
&& property.Converter == null)
{
property.Converter = property.MemberConverter = converter;
}
}
return contract;
}
}

And use it as follows:

var settings = new JsonSerializerSettings 
{
TypeNameHandling = TypeNameHandling.Auto,
ContractResolver = UntypedToTypedPropertyContractResolver.Instance,
};

var json = JsonConvert.SerializeObject(example, Formatting.Indented, settings);

var example2 = JsonConvert.DeserializeObject<Example>(json, settings);

In both cases the JSON created looks like:

{
"TypedProperty": 5,
"UntypedProperty": {
"$type": "Question38777588.TypeWrapper`1[[System.Guid, mscorlib]], Tile",
"Value": "e2983c59-5ec4-41cc-b3fe-34d9d0a97f22"
}
}

Json.NET - How to serialize property that value can be c# keyword?

You can use a custom converter to work with specific types and as a workaround this issue. This will also enable you to provide custom conversion to other types, not just your enum.

Take a look into Newtonsoft.Json docs about this here: https://www.newtonsoft.com/json/help/html/CustomJsonConverterGeneric.htm

Bellow is a sample (from their website):

public class VersionConverter : JsonConverter<Version>
{
public override void WriteJson(JsonWriter writer, Version value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}

public override Version ReadJson(JsonReader reader, Type objectType, Version existingValue, bool hasExistingValue, JsonSerializer serializer)
{
string s = (string)reader.Value;

return new Version(s);
}
}

You might use it directly on your code, on JsonConvert.SerializeObject and JsonConvert.DeserializeObject overrides that receive the custom converter, or you can register it globally, as shown on this other SO answer: Registering a custom JsonConverter globally in Json.Net

Serialize Property, but Do Not Deserialize Property in Json.Net

Simplest method would be to mark the real property as [JsonIgnore] and create a get-only proxy property:

    /// <summary>
/// Gets or sets the country code, and province or state code delimited by a vertical pipe: <c>US|MI</c>
/// </summary>
[JsonIgnore]
public string CountryProvinceState
{
get
{
return string.Format("{0}|{1}", this.CountryCode, this.ProvinceState);
}
set
{
if (!string.IsNullOrWhiteSpace(value) && value.Contains("|"))
{
string[] valueParts = value.Split('|');
if (valueParts.Length == 2)
{
this.CountryCode = valueParts[0];
this.ProvinceState = valueParts[1];
}
}
}
}

[JsonProperty("countryProvinceState")]
string ReadCountryProvinceState
{
get { return CountryProvinceState; }
}

The proxy property can be private if you desire.

Update

If you have to do this for lots of properties in lots of classes, it might be easier to create your own ContractResolver that checks for a custom attribute. If found, the attribute would signal that the property is get-only:

[System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = false)]
public class GetOnlyJsonPropertyAttribute : Attribute
{
}

public class GetOnlyContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property != null && property.Writable)
{
var attributes = property.AttributeProvider.GetAttributes(typeof(GetOnlyJsonPropertyAttribute), true);
if (attributes != null && attributes.Count > 0)
property.Writable = false;
}
return property;
}
}

Then use it like:

[JsonProperty("countryProvinceState")]
[GetOnlyJsonProperty]
public string CountryProvinceState { get; set; }

And then:

        var settings = new JsonSerializerSettings { ContractResolver = new GetOnlyContractResolver() };

var address = JsonConvert.DeserializeObject<Address>(jsonString, settings);


Related Topics



Leave a reply



Submit