How do deserialize JSON with non-standard (and varying) property names (in .NET)

I have to read a JSON stream (which I have no control over), which is in the form:

{"files":
    {
        "/some_file_path.ext": {"size":"1000", "data":"xxx", "data2":"yyy"},
        "/other_file_path.ext": {"size":"2000", "data":"xxx", "data2":"yyy"},
        "/another_file_path.ext": {"size":"3000", "data":"xxx", "data2":"yyy"},
    }
}

So, I have an object named files, which has a number of properties, which have 1) different names every time, 2) different number of them every time, and 3) names with characters which can't be used in C# properties.

How do I deserialize this?

I'm putting this into a Portable Library, so I can't use the JavaScriptSerializer, in System.Web.Script.Serialization, and I'm not sure about JSON.NET. I was hoping to use the standard DataContractJsonSerializer.


UPDATE: I've changed the sample data to be closer to the actual data, and corrected the JSON syntax in the area the wasn't important. (Still simplified quite a bit, but the other parts are fairly standard)


Solution 1:

You can model your "files" object as a Dictionary keyed by the JSON property name:

public class RootObject
{
    public Dictionary<string, PathData> files { get; set; }
}

public class PathData
{
    public int size { get; set; }
    public string data { get; set; }
    public string data2 { get; set; }
}

Then, only if you are using .Net 4.5 or later, you can deserialize using DataContractJsonSerializer, but you must first set DataContractJsonSerializerSettings.UseSimpleDictionaryFormat = true:

        var settings = new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true };
        var root = DataContractJsonSerializerHelper.GetObject<RootObject>(jsonString, settings);

With the helper method:

public static class DataContractJsonSerializerHelper
{
    public static T GetObject<T>(string json, DataContractJsonSerializer serializer = null)
    {
        using (var stream = GenerateStreamFromString(json))
        {
            var obj = (serializer ?? new DataContractJsonSerializer(typeof(T))).ReadObject(stream);
            return (T)obj;
        }
    }

    public static T GetObject<T>(string json, DataContractJsonSerializerSettings settings)
    {
        return GetObject<T>(json, new DataContractJsonSerializer(typeof(T), settings));
    }

    private static MemoryStream GenerateStreamFromString(string value)
    {
        return new MemoryStream(Encoding.Unicode.GetBytes(value ?? ""));
    }
}

Alternatively, you can install Json.NET and do:

        var root = JsonConvert.DeserializeObject<RootObject>(jsonString);

Json.NET automatically serializes dictionaries to JSON objects without needing to change settings.

Solution 2:

We need to first convert this Invalid JSON to a Valid JSON. So a Valid JSON should look like this

{
    "files": 
    {
        "FilePath" : "C:\\some\\file\\path",
        "FileData" : {
            "size": 1000,
            "data": "xxx",
            "data2": "yyy"
        },
        "FilePath" :"C:\\other\\file\\path",
        "FileData" : {
            "size": 2000,
            "data": "xxx",
            "data2": "yyy"
        },
        "FilePath" :"C:\\another\\file\\path",
        "FileData" : {
            "size": 3000,
            "data": "xxx",
            "data2": "yyy"
        }
    }
}

To make it a valid JSON we might use some string functions to make it looks like above. Such as

MyJSON = MyJSON.Replace("\\", "\\\\");
MyJSON = MyJSON.Replace("files", "\"files\"");
MyJSON = MyJSON.Replace("data:", "\"data:\"");
MyJSON = MyJSON.Replace("data2", "\"data2\"");
MyJSON = MyJSON.Replace(": {size", ",\"FileData\" : {\"size\"");
MyJSON = MyJSON.Replace("C:", "\"FilePath\" :\"C:");

Than we can create a class like below to read the

public class FileData
{
    public int size { get; set; }
    public string data { get; set; }
    public string data2 { get; set; }
}

public class Files
{
    public string FilePath { get; set; }
    public FileData FileData { get; set; }
}

public class RootObject
{
    public Files files { get; set; }
}