From a6108bc75a1755f6c057c4e25f4dc0ff1903390a Mon Sep 17 00:00:00 2001 From: josesimoes Date: Sat, 5 Jun 2021 09:22:16 +0100 Subject: [PATCH 1/6] Fix DateTime processing - Can handle 0001-01-01T00:00:00Z as the "null" value for DateTime. - Can handle wrong encoded data from Azure Twins. --- nanoFramework.Json/JsonConvert.cs | 25 +++++++++++++++++-------- nanoFramework.Json/JsonValue.cs | 27 ++++++++++++++++++--------- nanoFramework.Json/TimeExtensions.cs | 9 ++++++++- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/nanoFramework.Json/JsonConvert.cs b/nanoFramework.Json/JsonConvert.cs index d8c1753..f6c0481 100644 --- a/nanoFramework.Json/JsonConvert.cs +++ b/nanoFramework.Json/JsonConvert.cs @@ -1160,23 +1160,32 @@ private static LexToken GetNextTokenInternal() //Debug.Assert(ch == openQuote); var stringValue = sb.ToString(); - DateTime dtValue = DateTime.MinValue; + DateTime dtValue = DateTime.MaxValue; // check if this could be a DateTime value // min lenght is 18 for Java format: "Date(628318530718)": 18 if (stringValue.Length >= 18) { - try + // check for special case of "null" date + if(stringValue == "0001-01-01T00:00:00Z") { - dtValue = DateTimeExtensions.FromIso8601(stringValue); + dtValue = DateTime.MinValue; } - catch + + if (dtValue == DateTime.MaxValue) { - // intended, to catch failed conversion attempt + try + { + dtValue = DateTimeExtensions.FromIso8601(stringValue); + } + catch + { + // intended, to catch failed conversion attempt + } } - if (dtValue == DateTime.MinValue) + if (dtValue == DateTime.MaxValue) { try { @@ -1188,7 +1197,7 @@ private static LexToken GetNextTokenInternal() } } - if (dtValue == DateTime.MinValue) + if (dtValue == DateTime.MaxValue) { try { @@ -1200,7 +1209,7 @@ private static LexToken GetNextTokenInternal() } } - if (dtValue != DateTime.MinValue) + if (dtValue != DateTime.MaxValue) { return new LexToken() { TType = TokenType.Date, TValue = stringValue }; } diff --git a/nanoFramework.Json/JsonValue.cs b/nanoFramework.Json/JsonValue.cs index 09dcfc7..7ceb67a 100644 --- a/nanoFramework.Json/JsonValue.cs +++ b/nanoFramework.Json/JsonValue.cs @@ -18,18 +18,27 @@ public JsonValue(object value, bool isDateTime = false) { if (isDateTime) { - DateTime dtValue = DateTime.MinValue; - - try + DateTime dtValue = DateTime.MaxValue; + + // check for special case of "null" date + if ((string)value == "0001-01-01T00:00:00Z") { - dtValue = DateTimeExtensions.FromIso8601((string)value); + dtValue = DateTime.MinValue; } - catch + + if (dtValue == DateTime.MaxValue) { - // intended, to catch failed conversion attempt + try + { + dtValue = DateTimeExtensions.FromIso8601((string)value); + } + catch + { + // intended, to catch failed conversion attempt + } } - if (dtValue == DateTime.MinValue) + if (dtValue == DateTime.MaxValue) { try { @@ -41,7 +50,7 @@ public JsonValue(object value, bool isDateTime = false) } } - if (dtValue == DateTime.MinValue) + if (dtValue == DateTime.MaxValue) { try { @@ -53,7 +62,7 @@ public JsonValue(object value, bool isDateTime = false) } } - if (dtValue != DateTime.MinValue) + if (dtValue != DateTime.MaxValue) { Value = dtValue; } diff --git a/nanoFramework.Json/TimeExtensions.cs b/nanoFramework.Json/TimeExtensions.cs index 0b01720..51b89ec 100644 --- a/nanoFramework.Json/TimeExtensions.cs +++ b/nanoFramework.Json/TimeExtensions.cs @@ -100,7 +100,14 @@ public static DateTime FromIso8601(string date) string second = (parts.Length > 5) ? parts[5] : "0"; string ms = (parts.Length > 6) ? parts[6] : "0"; - DateTime dt = new DateTime(Convert.ToInt32(year), Convert.ToInt32(month), Convert.ToInt32(day), Convert.ToInt32(hour), Convert.ToInt32(minute), Convert.ToInt32(second), Convert.ToInt32(ms)); + // sanity check for bad milliseconds format + int milliseconds = Convert.ToInt32(ms); + if(milliseconds > 999) + { + milliseconds = 999; + } + + DateTime dt = new DateTime(Convert.ToInt32(year), Convert.ToInt32(month), Convert.ToInt32(day), Convert.ToInt32(hour), Convert.ToInt32(minute), Convert.ToInt32(second), milliseconds); if (utc) { From f51f5e6f07feec835274bcbb1ddd1a0ea4d68a90 Mon Sep 17 00:00:00 2001 From: josesimoes Date: Sat, 5 Jun 2021 10:16:33 +0100 Subject: [PATCH 2/6] Add workaround for Azure Twins properties starting with $ - Tries to find a property whose name starts with a _ instead. - Update Unit Tests to cover these. - Add new test classes to cover this. --- nanoFramework.Json.Test/JsonUnitTests.cs | 130 ++++++++++++++++++++++- nanoFramework.Json/JsonConvert.cs | 44 ++++++-- 2 files changed, 158 insertions(+), 16 deletions(-) diff --git a/nanoFramework.Json.Test/JsonUnitTests.cs b/nanoFramework.Json.Test/JsonUnitTests.cs index ea06530..0baedf6 100644 --- a/nanoFramework.Json.Test/JsonUnitTests.cs +++ b/nanoFramework.Json.Test/JsonUnitTests.cs @@ -501,14 +501,58 @@ public void SerializeAbstractClassTest() } [TestMethod] - public void CanSerializeAndDeserializeTwinProperties_01() + public void CanDeserializeAzureTwinProperties_01() { - var testString = "{\"desired\":{\"TimeToSleep\":5,\"$version\":2},\"reported\":{\"Firmware\":\"nanoFramework\",\"TimeToSleep\":2,\"$version\":94}}"; - var dserResult = (TwinProperties)JsonConvert.DeserializeObject(testString, typeof(TwinProperties)); + var twinPayload = (TwinProperties)JsonConvert.DeserializeObject(testString, typeof(TwinProperties)); - Assert.NotNull(dserResult, "Deserialization returned a null object"); + Assert.NotNull(twinPayload, "Deserialization returned a null object"); + + Assert.Equal(twinPayload.desired.TimeToSleep, 5, "desired.TimeToSleep doesn't match"); + Assert.Null(twinPayload.desired._metadata, "desired._metadata doesn't match"); + + Assert.Equal(twinPayload.reported.Firmware, "nanoFramework", "reported.Firmware doesn't match"); + Assert.Equal(twinPayload.reported.TimeToSleep, 2, "reported.TimeToSleep doesn't match"); + Assert.Null(twinPayload.reported._metadata, "reported._metadata doesn't match"); + + Debug.WriteLine(""); + } + + [TestMethod] + public void CanDeserializeAzureTwinProperties_02() + { + TwinPayload twinPayload = (TwinPayload)JsonConvert.DeserializeObject(s_AzureTwinsJsonTestPayload, typeof(TwinPayload)); + + Assert.NotNull(twinPayload, "Deserialization returned a null object"); + + Assert.Equal(twinPayload.authenticationType, "sas", "authenticationType doesn't match"); + Assert.Equal(twinPayload.statusUpdateTime.Ticks, DateTime.MinValue.Ticks, "statusUpdateTime doesn't match"); + Assert.Equal(twinPayload.cloudToDeviceMessageCount, 0, "cloudToDeviceMessageCount doesn't match"); + Assert.Equal(twinPayload.x509Thumbprint.Count, 2, "x509Thumbprint collection count doesn't match"); + Assert.Equal(twinPayload.version, 381, "version doesn't match"); + Assert.Equal(twinPayload.properties.desired.TimeToSleep, 30, "properties.desired.TimeToSleep doesn't match"); + Assert.Equal(twinPayload.properties.reported._metadata.Count, 3, "properties.reported._metadata collection count doesn't match"); + Assert.Equal(twinPayload.properties.desired._metadata.Count, 3, "properties.desired._metadata collection count doesn't match"); + + Debug.WriteLine(""); + } + + [TestMethod] + public void CanDeserializeAzureTwinProperties_03() + { + TwinPayload twinPayload = (TwinPayload)JsonConvert.DeserializeObject(s_AzureTwinsJsonTestPayload, typeof(TwinPayload)); + + Assert.NotNull(twinPayload, "Deserialization returned a null object"); + + Assert.Equal(twinPayload.authenticationType, "sas", "authenticationType doesn't match"); + Assert.Equal(twinPayload.statusUpdateTime.Ticks, DateTime.MinValue.Ticks, "statusUpdateTime doesn't match"); + Assert.Equal(twinPayload.cloudToDeviceMessageCount, 0, "cloudToDeviceMessageCount doesn't match"); + Assert.Equal(twinPayload.x509Thumbprint.Count, 2, "x509Thumbprint collection count doesn't match"); + Assert.Equal(twinPayload.version, 381, "version doesn't match"); + Assert.Equal(twinPayload.properties.desired.TimeToSleep, 30, "properties.desired.TimeToSleep doesn't match"); + Assert.Equal(twinPayload.properties.reported._metadata.Count, 3, "properties.reported._metadata collection count doesn't match"); + Assert.Equal(twinPayload.properties.desired._metadata.Count, 3, "properties.desired._metadata collection count doesn't match"); Debug.WriteLine(""); } @@ -560,15 +604,86 @@ public void CanDeserializeInvocationReceiveMessage_02() #region Test classes + private static string s_AzureTwinsJsonTestPayload = @"{ + ""deviceId"": ""nanoDeepSleep"", + ""etag"": ""AAAAAAAAAAc="", + ""deviceEtag"": ""Njc2MzYzMTQ5"", + ""status"": ""enabled"", + ""statusUpdateTime"": ""0001-01-01T00:00:00Z"", + ""connectionState"": ""Disconnected"", + ""lastActivityTime"": ""2021-06-03T05:52:41.4683112Z"", + ""cloudToDeviceMessageCount"": 0, + ""authenticationType"": ""sas"", + ""x509Thumbprint"": { + ""primaryThumbprint"": null, + ""secondaryThumbprint"": null + }, + ""modelId"": """", + ""version"": 381, + ""properties"": { + ""desired"": { + ""TimeToSleep"": 30, + ""$metadata"": { + ""$lastUpdated"": ""2021-06-03T05:37:11.8120413Z"", + ""$lastUpdatedVersion"": 7, + ""TimeToSleep"": { + ""$lastUpdated"": ""2021-06-03T05:37:11.8120413Z"", + ""$lastUpdatedVersion"": 7 + } + }, + ""$version"": 7 + }, + ""reported"": { + ""Firmware"": ""nanoFramework"", + ""TimeToSleep"": 30, + ""$metadata"": { + ""$lastUpdated"": ""2021-06-03T05:52:41.1232797Z"", + ""Firmware"": { + ""$lastUpdated"": ""2021-06-03T05:52:41.1232797Z"" + }, + ""TimeToSleep"": { + ""$lastUpdated"": ""2021-06-03T05:52:41.1232797Z"" + } + }, + ""$version"": 374 + } + }, + ""capabilities"": { + ""iotEdge"": false + } + }"; + + public class TwinPayload + { + public string deviceId { get; set; } + public string etag { get; set; } + public string status { get; set; } + public DateTime statusUpdateTime { get; set; } + public string connectionState { get; set; } + public DateTime lastActivityTime { get; set; } + public int cloudToDeviceMessageCount { get; set; } + public string authenticationType { get; set; } + public Hashtable x509Thumbprint { get; set; } + public string modelId { get; set; } + public int version { get; set; } + public TwinProperties properties { get; set; } + } + + + public class TwinPayloadProperties + { + public TwinProperties properties { get; set; } + } + public class TwinProperties { public Desired desired { get; set; } public Reported reported { get; set; } } - public class Desired { public int TimeToSleep { get; set; } + public Hashtable _metadata { get; set; } } public class Reported @@ -576,8 +691,13 @@ public class Reported public string Firmware { get; set; } public int TimeToSleep { get; set; } + + public Hashtable _metadata { get; set; } + + public int _version { get; set; } } + public class InvocationReceiveMessage { public int type { get; set; } diff --git a/nanoFramework.Json/JsonConvert.cs b/nanoFramework.Json/JsonConvert.cs index f6c0481..51cf4c0 100644 --- a/nanoFramework.Json/JsonConvert.cs +++ b/nanoFramework.Json/JsonConvert.cs @@ -204,6 +204,14 @@ private static object PopulateObject(JsonToken rootToken, Type rootType, string Debug.WriteLine($"{debugIndent} memberProperty.Name: {memberProperty?.Name ?? "null"} "); + string memberPropertyName = memberProperty?.Name; + + // workaround for for property names that start with '$' like Azure Twins + if (memberPropertyName[0] == '$') + { + memberPropertyName = "_" + memberProperty.Name.Substring(1); + } + // Figure out if we're dealing with a Field or a Property and handle accordingly Type memberType = null; FieldInfo memberFieldInfo = null; @@ -211,7 +219,7 @@ private static object PopulateObject(JsonToken rootToken, Type rootType, string MethodInfo memberPropGetMethod = null; bool memberIsProperty = false; - memberFieldInfo = rootType.GetField(memberProperty.Name); + memberFieldInfo = rootType.GetField(memberPropertyName); if (memberFieldInfo != null) { @@ -220,7 +228,7 @@ private static object PopulateObject(JsonToken rootToken, Type rootType, string } else { - memberPropGetMethod = rootType.GetMethod("get_" + memberProperty.Name); + memberPropGetMethod = rootType.GetMethod("get_" + memberPropertyName); if (memberPropGetMethod == null) { @@ -231,7 +239,7 @@ private static object PopulateObject(JsonToken rootToken, Type rootType, string else { memberType = memberPropGetMethod.ReturnType; - memberPropSetMethod = rootType.GetMethod("set_" + memberProperty.Name); + memberPropSetMethod = rootType.GetMethod("set_" + memberPropertyName); if (memberType == null) { @@ -241,7 +249,7 @@ private static object PopulateObject(JsonToken rootToken, Type rootType, string memberIsProperty = true; Debug.WriteLine($"{debugIndent} memberType: {memberType.Name} "); - Debug.WriteLine($"{debugIndent} memberPropGetMethod.Name: {memberPropGetMethod.Name} memberPropGetMethod.ReturnType: {memberPropGetMethod.ReturnType.Name}"); + Debug.WriteLine($"{debugIndent} memberPropGetMethod.Name: {memberPropertyName} memberPropGetMethod.ReturnType: {memberPropGetMethod.ReturnType.Name}"); } } @@ -255,11 +263,13 @@ private static object PopulateObject(JsonToken rootToken, Type rootType, string if (memberPath[memberPath.Length - 1] == '/') { - memberPath += memberProperty.Name; // Don't need to add a slash before appending rootElementType + // Don't need to add a slash before appending rootElementType + memberPath += memberPropertyName; } else { - memberPath = memberPath + '/' + memberProperty.Name; // Need to add a slash before appending rootElementType + // Need to add a slash before appending rootElementType + memberPath = memberPath + '/' + memberPropertyName; } object memberObject = null; @@ -276,7 +286,7 @@ private static object PopulateObject(JsonToken rootToken, Type rootType, string memberObject = table; - Debug.WriteLine($"{debugIndent} populated the {memberProperty.Name} Hashtable"); + Debug.WriteLine($"{debugIndent} populated the {memberPropertyName} Hashtable"); } else { @@ -292,7 +302,7 @@ private static object PopulateObject(JsonToken rootToken, Type rootType, string memberFieldInfo.SetValue(rootInstance, memberObject); } - Debug.WriteLine($"{debugIndent} successfully initialized member {memberProperty.Name} to memberObject"); + Debug.WriteLine($"{debugIndent} successfully initialized member {memberPropertyName} to memberObject"); } else if (memberProperty.Value is JsonValue) { @@ -302,9 +312,11 @@ private static object PopulateObject(JsonToken rootToken, Type rootType, string if (memberType != typeof(DateTime)) { Debug.WriteLine($"{debugIndent} attempting to set rootInstance by invoking this member's set method for properties or SetValue() for fields"); + if (((JsonValue)memberProperty.Value).Value == null) { Debug.WriteLine($"{debugIndent} memberProperty.Value is null"); + if (memberIsProperty) { if (!memberPropGetMethod.ReturnType.IsValueType) @@ -319,9 +331,11 @@ private static object PopulateObject(JsonToken rootToken, Type rootType, string case "Single": memberPropSetMethod.Invoke(rootInstance, new object[] { Single.NaN }); break; + case "Double": memberPropSetMethod.Invoke(rootInstance, new object[] { Double.NaN }); break; + default: break; } @@ -333,29 +347,36 @@ private static object PopulateObject(JsonToken rootToken, Type rootType, string object obj = null; memberFieldInfo.SetValue(rootInstance, obj); } - Debug.WriteLine($"{debugIndent} successfully initialized member {memberProperty.Name} to null"); + Debug.WriteLine($"{debugIndent} successfully initialized member {memberPropertyName} to null"); } else { if (memberIsProperty) { JsonValue val = (JsonValue)memberProperty.Value; + Debug.WriteLine($"{debugIndent} setting value with memberPropSetMethod: {memberPropSetMethod.Name} Declaring Type: {memberPropSetMethod.DeclaringType} Value: {((JsonValue)memberProperty.Value).Value}"); + Debug.WriteLine($"{debugIndent} memberProperty.Value.Value.Type: {val.Value.GetType().Name} memberProperty.Value.Value: {val.Value}"); + if (val.Value.GetType() != memberType) { Debug.WriteLine($"{debugIndent} need to change memberProperty.Value.Value.Type to {memberType} to match memberPropGetMethod.ReturnType - why are these are different?!?"); + switch (memberType.Name) { case nameof(Int16): memberPropSetMethod.Invoke(rootInstance, new object[] { Convert.ToInt16(val.Value.ToString()) }); break; + case nameof(Byte): memberPropSetMethod.Invoke(rootInstance, new object[] { Convert.ToByte(val.Value.ToString()) }); break; + case nameof(Single): memberPropSetMethod.Invoke(rootInstance, new object[] { Convert.ToSingle(val.Value.ToString()) }); break; + default: memberPropSetMethod.Invoke(rootInstance, new object[] { ((JsonValue)memberProperty.Value).Value }); break; @@ -370,7 +391,8 @@ private static object PopulateObject(JsonToken rootToken, Type rootType, string { memberFieldInfo.SetValue(rootInstance, ((JsonValue)memberProperty.Value).Value); } - Debug.WriteLine($"{debugIndent} successfully initialized member {memberProperty.Name} to {((JsonValue)memberProperty.Value).Value} "); + + Debug.WriteLine($"{debugIndent} successfully initialized member {memberPropertyName} to {((JsonValue)memberProperty.Value).Value} "); } } else @@ -384,7 +406,7 @@ private static object PopulateObject(JsonToken rootToken, Type rootType, string memberFieldInfo.SetValue(rootInstance, ((JsonValue)memberProperty.Value).Value); } - Debug.WriteLine($"{debugIndent} successfully initialized member {memberProperty.Name} to {(JsonValue)memberProperty.Value} "); + Debug.WriteLine($"{debugIndent} successfully initialized member {memberPropertyName} to {(JsonValue)memberProperty.Value} "); } } From 4549a12baa3edeccb49bd9045fefb3e868a82396 Mon Sep 17 00:00:00 2001 From: josesimoes Date: Sat, 5 Jun 2021 10:50:28 +0100 Subject: [PATCH 3/6] Improve handling of nested hastables --- nanoFramework.Json/JsonConvert.cs | 117 +++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 1 deletion(-) diff --git a/nanoFramework.Json/JsonConvert.cs b/nanoFramework.Json/JsonConvert.cs index 51cf4c0..8479763 100644 --- a/nanoFramework.Json/JsonConvert.cs +++ b/nanoFramework.Json/JsonConvert.cs @@ -281,7 +281,18 @@ private static object PopulateObject(JsonToken rootToken, Type rootType, string foreach (JsonPropertyAttribute v in ((JsonObjectAttribute)memberProperty.Value).Members) { - table.Add(v.Name, v.Value); + if (v.Value is JsonValue) + { + table.Add(v.Name, ((JsonValue)v.Value).Value); + } + else if (v.Value is JsonObjectAttribute) + { + table.Add(v.Name, PopulateHashtable(v.Value)); + } + else if (v.Value is JsonArrayAttribute) + { + throw new NotImplementedException(); + } } memberObject = table; @@ -846,6 +857,110 @@ private static ArrayList PopulateArrayList(JsonToken rootToken) return result; } + private static Hashtable PopulateHashtable(JsonToken rootToken) + { + var result = new Hashtable(); + + // Process all members for this rootObject + Debug.WriteLine($"{debugIndent} Entering rootObject.Members loop "); + + if (rootToken is JsonObjectAttribute) + { + var rootObject = (JsonObjectAttribute)rootToken; + + foreach (var m in rootObject.Members) + { + Debug.WriteLine($"{debugIndent} Process rootObject.Member"); + + var memberProperty = (JsonPropertyAttribute)m; + + if (memberProperty == null) + { + Debug.WriteLine($"memberProperty is null and can't be"); + + throw new NotSupportedException(); + } + + Debug.WriteLine($"{debugIndent} memberProperty.Name: {memberProperty?.Name ?? "null"} "); + + // Process the member based on JObject, JValue, or JArray + if (memberProperty.Value is JsonObjectAttribute) + { + // Call PopulateObject() for this member - i.e. recursion + Debug.WriteLine($"{debugIndent} memberProperty.Value is JObject"); + + throw new NotImplementedException(); + + Debug.WriteLine($"{debugIndent} successfully initialized member {memberProperty.Name} to memberObject"); + } + else if (memberProperty.Value is JsonValue) + { + if (memberProperty.Value is JsonValue) + { + result.Add(memberProperty.Name, ((JsonValue)memberProperty.Value).Value); + } + else if (memberProperty.Value is JsonObjectAttribute) + { + result.Add(memberProperty.Name, PopulateHashtable((JsonObjectAttribute)memberProperty.Value)); + } + else if (memberProperty.Value is JsonArrayAttribute) + { + throw new NotImplementedException(); + } + } + else if (memberProperty.Value is JsonArrayAttribute) + { + Debug.WriteLine($"{debugIndent} memberProperty.Value is a JArray"); + + // Create a JArray (memberValueArray) to hold the contents of memberProperty.Value + var memberValueArray = (JsonArrayAttribute)memberProperty.Value; + + // Create a temporary ArrayList memberValueArrayList - populate this as the memberItems are parsed + var memberValueArrayList = new ArrayList(); + + // Create a JToken[] array for Items associated for this memberProperty.Value + JsonToken[] memberItems = memberValueArray.Items; + + //Debug.WriteLine($"{debugIndent} copy {memberItems.Length} memberItems from memberValueArray into memberValueArrayList - call PopulateObject() for items that aren't JValue"); + + foreach (JsonToken item in memberItems) + { + if (item is JsonValue) + { + memberValueArrayList.Add(((JsonValue)item).Value); + } + else if (item is JsonToken) + { + throw new NotImplementedException(); + } + else + { + Debug.WriteLine($"{debugIndent} item is not a JToken or a JValue - this case is not handled"); + } + } + + Debug.WriteLine($"{debugIndent} {memberItems.Length} memberValueArray.Items copied into memberValueArrayList - i.e. contents of memberProperty.Value"); + + // add to main table + result.Add(memberProperty.Name, memberValueArrayList); + + Debug.WriteLine($"{debugIndent} populated the rootInstance object with the contents of targetArray"); + } + } + } + else if (rootToken is JsonArrayAttribute) + { + throw new NotImplementedException(); + } + else + { + throw new NotImplementedException(); + } + + return result; + } + + // Trying to deserialize a stream in nanoFramework is problematic. // as Stream.Peek() has not been implemented in nanoFramework // Therefore, read all input into the static jsonBytes[] and use jsonPos to keep track of where we are when parsing the input From a63e15f19e8859fc48a0e1eba72a0889078d075b Mon Sep 17 00:00:00 2001 From: josesimoes Date: Sat, 5 Jun 2021 11:02:15 +0100 Subject: [PATCH 4/6] Add new Unit Test --- nanoFramework.Json.Test/JsonUnitTests.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/nanoFramework.Json.Test/JsonUnitTests.cs b/nanoFramework.Json.Test/JsonUnitTests.cs index 0baedf6..e7fe5a3 100644 --- a/nanoFramework.Json.Test/JsonUnitTests.cs +++ b/nanoFramework.Json.Test/JsonUnitTests.cs @@ -557,6 +557,20 @@ public void CanDeserializeAzureTwinProperties_03() Debug.WriteLine(""); } + [TestMethod] + public void CanDeserializeAzureTwinProperties_04() + { + TwinPayloadProperties twinPayload = (TwinPayloadProperties)JsonConvert.DeserializeObject(s_AzureTwinsJsonTestPayload, typeof(TwinPayloadProperties)); + + Assert.NotNull(twinPayload, "Deserialization returned a null object"); + + Assert.Equal(twinPayload.properties.desired.TimeToSleep, 30, "properties.desired.TimeToSleep doesn't match"); + Assert.Equal(twinPayload.properties.reported._metadata.Count, 3, "properties.reported._metadata collection count doesn't match"); + Assert.Equal(twinPayload.properties.desired._metadata.Count, 3, "properties.desired._metadata collection count doesn't match"); + + Debug.WriteLine(""); + } + [TestMethod] public void CanDeserializeInvocationReceiveMessage_01() { From fc7ac2b336a06712c6ce8a55487fe55ffff9a47e Mon Sep 17 00:00:00 2001 From: josesimoes Date: Sat, 5 Jun 2021 11:03:30 +0100 Subject: [PATCH 5/6] Remove null check --- nanoFramework.Json/JsonConvert.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nanoFramework.Json/JsonConvert.cs b/nanoFramework.Json/JsonConvert.cs index 8479763..80097f6 100644 --- a/nanoFramework.Json/JsonConvert.cs +++ b/nanoFramework.Json/JsonConvert.cs @@ -202,9 +202,9 @@ private static object PopulateObject(JsonToken rootToken, Type rootType, string var memberProperty = (JsonPropertyAttribute)m; - Debug.WriteLine($"{debugIndent} memberProperty.Name: {memberProperty?.Name ?? "null"} "); + Debug.WriteLine($"{debugIndent} memberProperty.Name: {memberProperty.Name ?? "null"} "); - string memberPropertyName = memberProperty?.Name; + string memberPropertyName = memberProperty.Name; // workaround for for property names that start with '$' like Azure Twins if (memberPropertyName[0] == '$') From c3fcc0a0137b2b9e7dc61f5c8ac1f38f4ef3274f Mon Sep 17 00:00:00 2001 From: josesimoes Date: Sat, 5 Jun 2021 11:26:54 +0100 Subject: [PATCH 6/6] Fix errors reported by sonarcloud --- nanoFramework.Json/JsonConvert.cs | 50 +++++++++++++++---------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/nanoFramework.Json/JsonConvert.cs b/nanoFramework.Json/JsonConvert.cs index 80097f6..71ee90b 100644 --- a/nanoFramework.Json/JsonConvert.cs +++ b/nanoFramework.Json/JsonConvert.cs @@ -281,15 +281,15 @@ private static object PopulateObject(JsonToken rootToken, Type rootType, string foreach (JsonPropertyAttribute v in ((JsonObjectAttribute)memberProperty.Value).Members) { - if (v.Value is JsonValue) + if (v.Value is JsonValue jsonValue) { - table.Add(v.Name, ((JsonValue)v.Value).Value); + table.Add(v.Name, (jsonValue).Value); } - else if (v.Value is JsonObjectAttribute) + else if (v.Value is JsonObjectAttribute jsonObjectAttribute) { - table.Add(v.Name, PopulateHashtable(v.Value)); + table.Add(v.Name, PopulateHashtable(jsonObjectAttribute)); } - else if (v.Value is JsonArrayAttribute) + else if (v.Value is JsonArrayAttribute jsonArrayAttribute) { throw new NotImplementedException(); } @@ -802,12 +802,12 @@ private static ArrayList PopulateArrayList(JsonToken rootToken) mainTable.Add(memberProperty.Name, ((JsonValue)memberProperty.Value).Value); } - else if (memberProperty.Value is JsonArrayAttribute) + else if (memberProperty.Value is JsonArrayAttribute jsonArrayAttribute) { Debug.WriteLine($"{debugIndent} memberProperty.Value is a JArray"); // Create a JArray (memberValueArray) to hold the contents of memberProperty.Value - var memberValueArray = (JsonArrayAttribute)memberProperty.Value; + var memberValueArray = jsonArrayAttribute; // Create a temporary ArrayList memberValueArrayList - populate this as the memberItems are parsed var memberValueArrayList = new ArrayList(); @@ -819,11 +819,11 @@ private static ArrayList PopulateArrayList(JsonToken rootToken) foreach (JsonToken item in memberItems) { - if (item is JsonValue) + if (item is JsonValue jsonValue) { - memberValueArrayList.Add(((JsonValue)item).Value); + memberValueArrayList.Add((jsonValue).Value); } - else if (item is JsonToken) + else if (item is JsonToken jsonToken) { throw new NotImplementedException(); } @@ -864,11 +864,9 @@ private static Hashtable PopulateHashtable(JsonToken rootToken) // Process all members for this rootObject Debug.WriteLine($"{debugIndent} Entering rootObject.Members loop "); - if (rootToken is JsonObjectAttribute) + if (rootToken is JsonObjectAttribute rootTokenObjectAttribute) { - var rootObject = (JsonObjectAttribute)rootToken; - - foreach (var m in rootObject.Members) + foreach (var m in rootTokenObjectAttribute.Members) { Debug.WriteLine($"{debugIndent} Process rootObject.Member"); @@ -884,7 +882,7 @@ private static Hashtable PopulateHashtable(JsonToken rootToken) Debug.WriteLine($"{debugIndent} memberProperty.Name: {memberProperty?.Name ?? "null"} "); // Process the member based on JObject, JValue, or JArray - if (memberProperty.Value is JsonObjectAttribute) + if (memberProperty.Value is JsonObjectAttribute memberPropertyValue) { // Call PopulateObject() for this member - i.e. recursion Debug.WriteLine($"{debugIndent} memberProperty.Value is JObject"); @@ -893,27 +891,27 @@ private static Hashtable PopulateHashtable(JsonToken rootToken) Debug.WriteLine($"{debugIndent} successfully initialized member {memberProperty.Name} to memberObject"); } - else if (memberProperty.Value is JsonValue) + else if (memberProperty.Value is JsonValue memberPropertyJsonValue) { - if (memberProperty.Value is JsonValue) + if (memberPropertyJsonValue.Value is JsonValue jsonValue) { - result.Add(memberProperty.Name, ((JsonValue)memberProperty.Value).Value); + result.Add(memberProperty.Name, jsonValue.Value); } - else if (memberProperty.Value is JsonObjectAttribute) + else if (memberPropertyJsonValue.Value is JsonObjectAttribute jsonObjectAttribute) { - result.Add(memberProperty.Name, PopulateHashtable((JsonObjectAttribute)memberProperty.Value)); + result.Add(memberProperty.Name, PopulateHashtable(jsonObjectAttribute)); } - else if (memberProperty.Value is JsonArrayAttribute) + else if (memberProperty.Value is JsonArrayAttribute jsonArrayAttribute) { throw new NotImplementedException(); } } - else if (memberProperty.Value is JsonArrayAttribute) + else if (memberProperty.Value is JsonArrayAttribute jsonArrayAttribute) { Debug.WriteLine($"{debugIndent} memberProperty.Value is a JArray"); // Create a JArray (memberValueArray) to hold the contents of memberProperty.Value - var memberValueArray = (JsonArrayAttribute)memberProperty.Value; + var memberValueArray = jsonArrayAttribute; // Create a temporary ArrayList memberValueArrayList - populate this as the memberItems are parsed var memberValueArrayList = new ArrayList(); @@ -925,11 +923,11 @@ private static Hashtable PopulateHashtable(JsonToken rootToken) foreach (JsonToken item in memberItems) { - if (item is JsonValue) + if (item is JsonValue jsonValue) { - memberValueArrayList.Add(((JsonValue)item).Value); + memberValueArrayList.Add(jsonValue); } - else if (item is JsonToken) + else if (item is JsonToken jsonToken) { throw new NotImplementedException(); }