I need to convert my class to JSON and I use Json.NET. But I can have different JSON structures, like:
{
name: "Name",
type: "simple1",
value: 100
};
or
{
name: "Name",
type: {
optional1: {
setting1: "s1",
setting2: "s2",
///etc.
},
value: 100
};
My C# code is:
public class Configuration
{
[JsonProperty(PropertyName = "name")]
public string Name{ get; set; }
[JsonProperty(PropertyName = "type")]
public MyEnumTypes Type { get; set; }
public OptionalType TypeAdditionalData { get; set; }
[JsonProperty(PropertyName = "value")]
public int Value { get; set; }
public bool ShouldSerializeType()
{
OptionalSettingsAttribute optionalSettingsAttr = this.Type.GetAttributeOfType<OptionalSettingsAttribute>();
return optionalSettingsAttr == null;
}
public bool ShouldSerializeTypeAdditionalData()
{
OptionalSettingsAttribute optionalSettingsAttr = this.Type.GetAttributeOfType<OptionalSettingsAttribute>();
return optionalSettingsAttr != null;
}
}
public enum MyEnumTypes
{
[EnumMember(Value = "simple1")]
Simple1,
[EnumMember(Value = "simple2")]
Simple2,
[OptionalSettingsAttribute]
[EnumMember(Value = "optional1")]
Optional1,
[EnumMember(Value = "optional2")]
[OptionalSettingsAttribute]
Optional2
}
My idea was when Configuration.Type
- value hasn't attribute OptionalSettingsAttribute
- to serialize it as type: "simple1"
. Otherwise - to use Configuration.Type
- value as type's value key (type: { optional1: {} }
) and value in Configuration.TypeAdditionalData
as optional1
- value (like 2 simple JSON above).
I tried to create a custom Converter, like:
public class ConfigurationCustomConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Configuration).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return serializer.Deserialize<Configuration>(reader);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
//my changes here
serializer.Serialize(writer, value);
}
But when I add [JsonConverter(typeof(ConfigurationCustomConverter))]
attribute to Configuration
class:
[JsonConverter(typeof(ConfigurationCustomConverter))]
public class Configuration
and called JsonConvert.SerializeObject(configurationObj);
I received next error:
Self referencing loop detected with type 'Configuration'. Path ''.
Do you have any ideas how to change my code to serialize my class to 2 different JSON structures? Note: I won't use the same class to deserialize the JSON.
Thank you!
In C#, enums are backed by an integer. Most tools will serialize or save your enums using that integer value.
Text. Json is much faster than the Newtonsoft. Json.
Deserializes the JSON to the specified . NET type. Deserializes the JSON to the specified . NET type using a collection of JsonConverter.
Specifies the settings on a JsonSerializer object.
The reason you are getting the Self referencing loop detected
exception is that the WriteJson
method of your converter is calling itself recursively. When you apply a converter to a type using [JsonConverter(typeof(ConfigurationCustomConverter))]
, the WriteJson()
method will unconditionally replace Json.NET's default implementation. Thus your inner call:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
//my changes here
serializer.Serialize(writer, value);
}
would cause a stack overflow. Json.NET notices this and instead throws the exception you see. For more details, see JSON.Net throws StackOverflowException when using [JsonConvert()]. Setting ReferenceLoopHandling.Ignore
simply causes the infinite recursion to be skipped, leaving your object empty.
You have a few options to solve this problem:
You could manually write all property names and values other than Type
and TypeAdditionalData
then write out the custom "type"
property last. For instance:
[JsonConverter(typeof(ConfigurationConverter))]
public class Configuration
{
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
public MyEnumTypes Type { get; set; }
public OptionalType TypeAdditionalData { get; set; }
[JsonProperty(PropertyName = "value")]
public int Value { get; set; }
}
class ConfigurationConverter : JsonConverter
{
const string typeName = "type";
public override bool CanConvert(Type objectType)
{
return typeof(Configuration).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var config = (existingValue as Configuration ?? (Configuration)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator());
// Populate the regular property values.
var obj = JObject.Load(reader);
var type = obj.RemoveProperty(typeName);
using (var subReader = obj.CreateReader())
serializer.Populate(subReader, config);
// Populate Type and OptionalType
if (type is JValue) // Primitive value
{
config.Type = type.ToObject<MyEnumTypes>(serializer);
}
else
{
var dictionary = type.ToObject<Dictionary<MyEnumTypes, OptionalType>>(serializer);
if (dictionary.Count > 0)
{
config.Type = dictionary.Keys.First();
config.TypeAdditionalData = dictionary.Values.First();
}
}
return config;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var config = (Configuration)value;
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(config.GetType());
writer.WriteStartObject();
foreach (var property in contract.Properties
.Where(p => p.Writable && (p.ShouldSerialize == null || p.ShouldSerialize(config)) && !p.Ignored))
{
if (property.UnderlyingName == "Type" || property.UnderlyingName == "TypeAdditionalData")
continue;
var propertyValue = property.ValueProvider.GetValue(config);
if (propertyValue == null && serializer.NullValueHandling == NullValueHandling.Ignore)
continue;
writer.WritePropertyName(property.PropertyName);
serializer.Serialize(writer, propertyValue);
}
writer.WritePropertyName(typeName);
if (config.Type.GetCustomAttributeOfEnum<OptionalSettingsAttribute>() == null)
{
serializer.Serialize(writer, config.Type);
}
else
{
var dictionary = new Dictionary<MyEnumTypes, OptionalType>
{
{ config.Type, config.TypeAdditionalData },
};
serializer.Serialize(writer, dictionary);
}
writer.WriteEndObject();
}
}
public class OptionalType
{
public string setting1 { get; set; }
}
public class OptionalSettingsAttribute : System.Attribute
{
public OptionalSettingsAttribute()
{
}
}
[JsonConverter(typeof(StringEnumConverter))]
public enum MyEnumTypes
{
[EnumMember(Value = "simple1")]
Simple1,
[EnumMember(Value = "simple2")]
Simple2,
[OptionalSettingsAttribute]
[EnumMember(Value = "optional1")]
Optional1,
[EnumMember(Value = "optional2")]
[OptionalSettingsAttribute]
Optional2
}
public static class EnumExtensions
{
public static TAttribute GetCustomAttributeOfEnum<TAttribute>(this Enum value)
where TAttribute : System.Attribute
{
var type = value.GetType();
var memInfo = type.GetMember(value.ToString());
return memInfo[0].GetCustomAttribute<TAttribute>();
}
}
public static class JsonExtensions
{
public static JToken RemoveProperty(this JObject obj, string name)
{
if (obj == null)
return null;
var property = obj.Property(name);
if (property == null)
return null;
var value = property.Value;
property.Remove();
property.Value = null;
return value;
}
}
Notice I added [JsonConverter(typeof(StringEnumConverter))]
to your enum. This ensures the type is always written as a string.
Sample fiddle.
You could disable recursive calls to the converter via the technique shown in JSON.Net throws StackOverflowException when using [JsonConvert()], generate a default serialization, modify it as required, and write it out.
You could avoid the use of a converter entirely by marking Type
and TypeAdditionalData
as [JsonIgnore]
and introducing an additional private property to serialize and deserialize "type"
:
public class Configuration
{
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
[JsonIgnore]
public MyEnumTypes Type { get; set; }
[JsonIgnore]
public OptionalType TypeAdditionalData { get; set; }
[JsonProperty("type")]
JToken SerializedType
{
get
{
if (Type.GetCustomAttributeOfEnum<OptionalSettingsAttribute>() == null)
{
return JToken.FromObject(Type);
}
else
{
var dictionary = new Dictionary<MyEnumTypes, OptionalType>
{
{ Type, TypeAdditionalData },
};
return JToken.FromObject(dictionary);
}
}
set
{
if (value == null || value.Type == JTokenType.Null)
{
TypeAdditionalData = null;
Type = default(MyEnumTypes);
}
else if (value is JValue)
{
Type = value.ToObject<MyEnumTypes>();
}
else
{
var dictionary = value.ToObject<Dictionary<MyEnumTypes, OptionalType>>();
if (dictionary.Count > 0)
{
Type = dictionary.Keys.First();
TypeAdditionalData = dictionary.Values.First();
}
}
}
}
[JsonProperty(PropertyName = "value")]
public int Value { get; set; }
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With