'Custom JsonConvertor that also serializes it's value minus one of it's properties
I am having trouble with the requirement to serialize an object in a specific way whereby the object id value becomes the key and the rest of the object forms the value.
Simplified class to be serialized:
[JsonConverter(typeof(FieldTypeConvertor))]
public class FieldType {
public string Id { get; set; }
public string Condition { get; set; }
public string FieldType { get; set; }
public string Label { get; set; }
public string Options { get; set; }
}
Here is my JsonConvertor WriteJson method:
public override void WriteJson(JsonWriter writer, UmbracoFormFieldDto value, JsonSerializer serializer)
{
var props = value.GetType().GetProperties();
var idProp = props.FirstOrDefault(p => p.Name.Equals("id", StringComparison.OrdinalIgnoreCase));
var key = idProp.GetValue(value, null).ToString();
var newObj = JsonConvert.SerializeObject(value, new JsonSerializerSettings()
{ ContractResolver = new IgnorePropertiesResolver(new[] { "id" }) });
var container = new JObject { { key, newObj } };
container.WriteTo(writer);
}
I get why I end up with a StackOverflow but do not know how to avoid it in order generate the output I need which is the following:
"idValueFromOriginalObj": {
"condition": "propValue",
"fieldype": "propValue",
"label": "propValue",
"options": "propValue"
}
Essentially, the value of id in the original object becomes the key in the serialized object and the rest of the original object properties form the value.
Solution 1:[1]
Your problem is that, inside JsonConverter.ReadJson(), you are attempting to recursively serialize your value object, but since the converter is applied directly to the type using [JsonConverter(typeof(TConverter))], you are getting a stack overflow.
There are several options to disable a converter for recursive serialization; JSON.Net throws StackOverflowException when using [JsonConvert()] details some of them. However, since you are already using a custom contract resolver IgnorePropertiesResolver to ignore properties named "id", you might enhance the resolver to also ignore converters of type FieldTypeConvertor. The following should do the trick:
public class IgnorePropertiesResolver : DefaultContractResolver
{
readonly HashSet<string> propertiesToIgnore;
readonly HashSet<Type> converterTypesToIgnore;
public IgnorePropertiesResolver(IEnumerable<string> propertiesToIgnore, IEnumerable<Type> converterTypesToIgnore) : base() =>
(this.propertiesToIgnore, this.converterTypesToIgnore) =
((propertiesToIgnore ?? throw new ArgumentNullException()).ToHashSet(StringComparer.OrdinalIgnoreCase),
(converterTypesToIgnore ?? throw new ArgumentNullException()).ToHashSet());
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (propertiesToIgnore.Contains(member.Name))
property.Ignored = true;
if (property.Converter != null && converterTypesToIgnore.Contains(property.Converter.GetType()))
property.Converter = null;
return property;
}
protected override JsonContract CreateContract(Type objectType)
{
var contract = base.CreateContract(objectType);
if (contract.Converter != null && converterTypesToIgnore.Contains(contract.Converter.GetType()))
contract.Converter = null;
return contract;
}
};
Then modify FieldTypeConvertor as follows:
public sealed class FieldTypeConvertor : JsonConverter<UmbracoFormFieldDto>
{
static readonly IContractResolver innerResolver = new IgnorePropertiesResolver(new [] { "id" }, new [] { typeof(FieldTypeConvertor) })
{
NamingStrategy = new CamelCaseNamingStrategy(),
};
public override void WriteJson(JsonWriter writer, UmbracoFormFieldDto value, JsonSerializer serializer)
{
var props = value.GetType().GetProperties();
var idProp = props.FirstOrDefault(p => p.Name.Equals("id", StringComparison.OrdinalIgnoreCase));
var key = idProp.GetValue(value, null).ToString();
writer.WriteStartObject();
writer.WritePropertyName(key);
JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = innerResolver }).Serialize(writer, value);
writer.WriteEndObject();
}
public override UmbracoFormFieldDto ReadJson(JsonReader reader, Type objectType, UmbracoFormFieldDto existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException();
}
And your model will be serialized as required:
{
"idValueFromOriginalObj": {
"condition": "propValue",
"fieldType": "propValue",
"label": "propValue",
"options": "propValue"
}
}
Notes:
Newtonsoft recommends you cache the contract resolver for best performance.
You should inherit from
DefaultContractResolverinstead ofCamelCasePropertyNamesContractResolverfor reasons explained in Json.Net: Html Helper Method not regenerating.For performance reasons, I eliminated the intermediate serialization to
JObjectand instead serialized directly to the incomingJsonWriter.
Demo fiddle here.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | dbc |
