Your "goal" JSON is tricky to handle because the treatment of the SubDataMappers
list is different depending on whether the children have a non-null DataMapperProperty
or a non-empty list of SubDataMappers
. In the former case, you want it rendered as an object containing one property per child DataMapper
; in the latter, as an array of objects containing one DataMapper
each. Also, I see you are using the Name
property of the DataMapper
as a key in the JSON rather than as the value of a well-known property. Given these two constraints, I think the best plan of attack is to make a JsonConverter
that operates on a list of DataMappers
rather than a single instance. Otherwise, the converter code is going to get pretty messy. If that is acceptable, then the following converter should give you what you want:
public class DataMapperListConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(List<DataMapper>);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
List<DataMapper> list = (List<DataMapper>)value;
if (list.Any(dm => dm.DataMapperProperty != null))
{
JObject obj = new JObject(list.Select(dm =>
{
JToken val;
if (dm.DataMapperProperty != null)
val = JToken.FromObject(dm.DataMapperProperty, serializer);
else
val = JToken.FromObject(dm.SubDataMappers, serializer);
return new JProperty(dm.Name, val);
}));
obj.WriteTo(writer);
}
else
{
serializer.Serialize(writer,
list.Select(dm => new Dictionary<string, List<DataMapper>>
{
{ dm.Name, dm.SubDataMappers }
}));
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Object)
{
return token.Children<JProperty>()
.Select(jp =>
{
DataMapper mapper = new DataMapper { Name = jp.Name };
JToken val = jp.Value;
if (val["data-type"] != null)
mapper.DataMapperProperty = jp.Value.ToObject<DataMapperProperty>(serializer);
else
mapper.SubDataMappers = jp.Value.ToObject<List<DataMapper>>(serializer);
return mapper;
})
.ToList();
}
else if (token.Type == JTokenType.Array)
{
return token.Children<JObject>()
.SelectMany(jo => jo.Properties())
.Select(jp => new DataMapper
{
Name = jp.Name,
SubDataMappers = jp.Value.ToObject<List<DataMapper>>(serializer)
})
.ToList();
}
else
{
throw new JsonException("Unexpected token type: " + token.Type.ToString());
}
}
}
Assumptions:
- You will never be serializing a single
DataMapper
by itself; it will always be contained in a list.
DataMappers
can be nested to an arbitrary depth.
- A
DataMapper
will always have a non-null Name
, which is unique at each level.
- A
DataMapper
will never have both a non-null DataMapperProperty
and a non-empty list of SubDataMappers
.
- A
DataMapperProperty
will always have a non-null DataType
.
- A
DataMapper
will never have a Name
of data-type
.
If the last four assumptions do not hold true, then this JSON format will not work for what you are trying to do, and you will need to rethink.
To use the converter, you will need to add it to your serializer settings as shown below. Use the settings both when you serialize and deserialize. Remove the [JsonConverter]
attribute from the DataMapper
class.
var settings = new JsonSerializerSettings()
{
Converters = new List<JsonConverter> { new DataMapperListConverter() },
Formatting = Formatting.Indented
};
Here is a round-trip demo: https://dotnetfiddle.net/8KycXB