简体   繁体   中英

Custom Json Serializer for deep nested objects

I have a couple .NET classes that get generated that go three levels deep and I'd like to serialize them in a special format. So, I began writing a custom Json Serializer using Newtonsoft.Json.

I believe it will be difficult to fully explain, so I've posted the code along with the goal here: https://dotnetfiddle.net/CDGcMW

Essentially, there is an initial array that would contain objects and there would be properties for that object. The difficult part is that these properties are not known, thus is why I'm attempting to create a custom serializer.

Any help in determining how I can make the Json produced here https://dotnetfiddle.net/CDGcMW become the "goal" JSON that's commented out would be greatly appreciated.

Edit: Updated dotnetfiddle to a smaller example. Original is here: https://dotnetfiddle.net/dprfDu

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

You can achieve the deep nested serialization with JSON.NET by replacing all your typed classes with ExpandoObject. It works for me. Let me know if it works for you or need any sample to show you.

UPDATE:

Here is the working sample

https://dotnetfiddle.net/jtebDs

Hope this is what you would like see the output. Let me know if you have any questions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM