cFrozenDeath cFrozenDeath - 5 months ago 16
JSON Question

De-serialize JSON list of JSON serialized objects

I'm trying to de-serialize a

List<string>
which is in fact a
List<myClass>
whose objects have been serialized. Example:

static void Main(string[] args)
{
List<MyThirdClass> myThirdClass = new List<MyThirdClass>(new[] { new MyThirdClass { RoleId = 123, RoleName = "123" }, new MyThirdClass { RoleId = 234, RoleName = "234" } });
List<MySecondSerializedClass> mySecondSerializedClass = new List<MySecondSerializedClass>();
foreach (MyThirdClass thirdClass in myThirdClass)
{
MySecondSerializedClass secondClass = new MySecondSerializedClass { Roles = new List<string>() };
foreach (MyThirdClass tClass in myThirdClass)
{
secondClass.Roles.Add(JsonConvert.SerializeObject(tClass));
}
mySecondSerializedClass.Add(secondClass);
}
MyFirstSerializedClass firstClass = new MyFirstSerializedClass
{
Id = 1,
Name = "1",
Roles = mySecondSerializedClass
};

string serializedFirstClass = JsonConvert.SerializeObject(firstClass, Formatting.Indented);
MyFirstNonSerializedClass nonSerializedFirstClass = JsonConvert.DeserializeObject<MyFirstNonSerializedClass>(serializedFirstClass);
}

public class MyFirstSerializedClass
{
public int Id { get; set; }
public string Name { get; set; }
public List<MySecondSerializedClass> Roles { get; set; }
}

public class MyFirstNonSerializedClass
{
public int Id { get; set; }
public string Name { get; set; }
public List<MySecondNonSerializedClass> Roles { get; set; }
}

public class MySecondSerializedClass
{
public List<string> Roles { get; set; }
}

public class MySecondNonSerializedClass
{
public List<MyThirdClass> Roles { get; set; }
}

public class MyThirdClass
{
public int RoleId { get; set; }
public string RoleName { get; set; }
}


serializedFirstClass
returns a JSON like this:

{
"Id": 1,
"Name": "1",
"Roles": [
{
"Roles": [
"{\"RoleId\":123,\"RoleName\":\"123\"}",
"{\"RoleId\":234,\"RoleName\":\"234\"}"
]
},
{
"Roles": [
"{\"RoleId\":123,\"RoleName\":\"123\"}",
"{\"RoleId\":234,\"RoleName\":\"234\"}"
]
}
]
}


And trying to de-serialize it throws an Exception with the message:


Error converting value "{"RoleId":123,"RoleName":"123"}" to type 'ConsoleApplication1.Program+MyThirdClass'. Path 'Roles[0].Roles[0]', line 7, position 47.


Is there something I am doing wrong or any way to recursively de-serialize the MyFirstSerializedClass to MyFirstNonSerializedClass?

dbc dbc
Answer

You can do this with a single set of classes and an optional JsonConverter for MyThirdClass that reads the current JSON token as a string literal, then uses a nested serializer to deserialize that string literal from JSON as an instance of MyThirdClass. Add the converter to JsonSerializerSettings.Converters when you want to serialize or deserialize your class as an embedded string; leave it out to serialize as an object.

Thus:

public class MyFirstClass
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<MySecondClass> Roles { get; set; }
}

public class MySecondClass
{
    public List<MyThirdClass> Roles { get; set; }
}

public class MyThirdClass
{
    public int RoleId { get; set; }
    public string RoleName { get; set; }
}

public sealed class MyThirdClassStringConverter : JsonConverter
{
    readonly JsonSerializerSettings settings;

    public MyThirdClassStringConverter() : this(null) { }

    public MyThirdClassStringConverter(JsonSerializerSettings settings)
    {
        this.settings = settings;
    }

    JsonSerializer GetInnerSerializer()
    {
        var innerSerializer = JsonSerializer.CreateDefault(settings);
        for (int i = innerSerializer.Converters.Count - 1; i >= 0; i--)
            if (innerSerializer.Converters[i] is MyThirdClassStringConverter)
                innerSerializer.Converters.RemoveAt(i);
        return innerSerializer;
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(MyThirdClass).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var innerSerializer = GetInnerSerializer();
        if (reader.TokenType == JsonToken.String)
        {
            var s = reader.Value.ToString();
            using (var innerReader = new StringReader(s))
                return innerSerializer.Deserialize(innerReader, objectType);
        }
        else
        {
            return innerSerializer.Deserialize(reader, objectType);
        }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var innerSerializer = GetInnerSerializer();
        var sb = new StringBuilder();
        using (var innerWriter = new StringWriter(sb))
            innerSerializer.Serialize(innerWriter, value);
        writer.WriteValue(sb.ToString());
    }
}

Then, use it as follows:

var stringSettings = new JsonSerializerSettings { Converters = new[] { new MyThirdClassStringConverter() } };

// Deserialize JSON where MyThirdClass objects are embedded strings
var root = JsonConvert.DeserializeObject<MyFirstClass>(stringJson, stringSettings);

// Re-serialize to JSON where MyThirdClass objects are objects
var newNonStringJson = JsonConvert.SerializeObject(root, Formatting.Indented);
// Re-serialize to JSON where MyThirdClass objects are embedded strings
var newStringJson = JsonConvert.SerializeObject(root, Formatting.Indented, stringSettings);

Note that the converter should not be applied to MyThirdClass directly using [JsonConverter(typeof(MyThirdClassStringConverter))] as infinite recursion would result. The converter's ReadJson() method tests to see whether the current token is a string or object and adapts accordingly, so it can be used to deserialize JSON in either format.

Example fiddle.