Deserialize Dynamic JSON file C # NewtonSoft.JSON
Working on deserializing a dynamic JSON file that can contain 2 separate classes and I won't know what type of data will be in the array.
The problem is that I deserialize the root object of type "Base", the objects "subtest" are deserialized in "Subtest", but the array of "subtests" can be of type "Base" or type "Subtest".
QUESTION: How would I decide to programmatically determine that if an object contains a "subtest", I deserialize the base, and if it doesn't, it should deserialize to "Subtest"?
I really appreciate help on this since I'm on a short timeline.
(EDIT: Added comments to show what type of each object should be deserialized) Here's an example (JSON DATA):
{
// Deserializes to type "Base"
"host": "123456",
"last_time": "2014-09-15 07:04:49.205000",
"name": "myName",
"result": "FAIL",
"serial": "12345",
"start_time": "2014-09-15 06:53:36.976000",
// Deserializes to type "List<Subtest>"
"subtests": [
{
"data": {
"moredata": {
"ver": "123",
"real": 123
}
},
"description": "Description of Data",
"host": "123456",
"last_time": "2014-09-15 20:32:31.095000",
"name": "testname.py",
"result": "PASS",
"start_time": "2014-09-15 20:32:25.873000",
"version": "2.014.09.15"
},
{
// Nested within Subtest Array, Should deserialize to type "Base" again
"host": "123456",
"last_time": "2014-09-15 07:04:49.205000",
"name": "name of test suite",
"result": "FAIL",
"start_time": "2014-09-15 06:53:36.976000",
//Should deserialize to type "List<Subtest>"
"subtests": [
{
"description": "Description of Data",
"host": "123456",
"last_time": "2014-09-15 06:53:40.440000",
"name": "testname.py",
"result": "FAIL",
"start_time": "2014-09-15 06:53:36.976000",
"version": "2.014.09.15"
},
{
"description": "Test Description",
"host": "123456",
"last_time": "2014-09-15 06:54:34.905000",
"name": "testname.py",
"result": "PASS",
"start_time": "2014-09-15 06:54:34.827000",
"version": "2.014.09.15"
},
{
"host": "123456",
"last_time": "2014-09-15 06:55:01.156000",
"name": "testname.py",
"result": "FAIL",
"start_time": "2014-09-15 06:55:01.156000",
"version": "2.014.09.15"
},
],
"version": "1.45"
}
],
"version": "1.23"
}
Example (CODE):
public class Base{
public string host { get; set; }
public DateTime last_time { get; set; }
public string name { get; set; }
public string result { get; set; }
public string serial { get; set; }
public DateTime start_time { get; set; }
public List<Subtest> subtests { get; set; }
public string version { get; set; }
}
public class Subtest {
[JsonProperty("data")]
public JObject Data { get; set; } // CHECK
[JsonProperty("description")]
public string Description { get; set; } // CHECK
[JsonProperty("host")]
public string Host { get; set; }
[JsonProperty("info")]
public List<StatusDetails> Info { get; set; }
[JsonProperty("last_time")]
public DateTime LastRunTime { get; set; }
[JsonProperty("name")]
public string TestName { get; set; }
[JsonProperty("result")]
public string SubtestRunResult { get; set; }
[JsonProperty("start_time")]
public DateTime StartTime { get; set; }
[JsonProperty("trace")]
public List<object> Trace { get; set; }
[JsonProperty("version")]
public string Version { get; set; }
}
source to share
I would rework your classes to form a hierarchy. I'm probably missing the properties here, but you get the picture. The important bit is the converter.
public abstract class TestBase
{
public string Host { get; set; }
[JsonProperty("last_time")]
public DateTime LastTime { get; set; }
public string Name { get; set; }
public string Result { get; set; }
[JsonProperty("start_time")]
public DateTime StartTime { get; set; }
public string Version { get; set; }
}
public class TestSuite : TestBase
{
public string Serial { get; set; }
public List<TestBase> Subtests { get; set; }
}
public class Subtest : TestBase
{
public JObject Data { get; set; }
public string Description { get; set; }
}
Then you need a custom converter to choose the correct type based on the existence of the property subtests
:
public class TestBaseConverter : JsonConverter
{
public override object ReadJson(
JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
JObject obj = serializer.Deserialize<JObject>(reader);
TestBase result = null;
if (obj["subtests"] != null)
{
result = new TestSuite();
}
else
{
result = new Subtest();
}
serializer.Populate(obj.CreateReader(), result);
return result;
}
public override bool CanConvert(Type objectType)
{
return typeof(TestBase).IsAssignableFrom(objectType);
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(
JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException();
}
}
Then you will use it like this:
TestSuite suite = JsonConvert.DeserializeObject<TestSuite>(
json, new TestBaseConverter());
source to share
As a result, I added the property List<Subtest>
to the class Subtest
and checked for null in the recursive loop function foreach
. Not as pretty as I'd like, but better than parsing it apart and deserializing each Subtest
object individually.
private static void GetSubtest(List<Subtest> subtestList) {
foreach (var subtest in subtestList) {
if (subtest.Subtests != null) {
GetSubtest(subtest.Subtests);
}
else {
// add data to Vertica cluster
}
}
}
Long day, really appreciate everything you guys are trying to help. New to JSON, so I just couldn't get around it. Hope this helps someone else in the future. Just drop a comment here if you need more explanation.
source to share