diff --git a/nanoFramework.Json.Test/JsonSerializerOptionsTests.cs b/nanoFramework.Json.Test/JsonSerializerOptionsTests.cs index bf7f70a..40ccf7a 100644 --- a/nanoFramework.Json.Test/JsonSerializerOptionsTests.cs +++ b/nanoFramework.Json.Test/JsonSerializerOptionsTests.cs @@ -119,6 +119,32 @@ public void Can_serialize_and_deserialize_arrays_of_class_objects() OutputHelper.WriteLine(""); } + [TestMethod] + public void Can_deserialize_string_starting_with_unicode_escape() + { + + OutputHelper.WriteLine("Starting test..."); + + Console.WriteLine($"{TimeSpan.FromHours(1).TotalMilliseconds}"); + + JsonTestCompany test = new JsonTestCompany + { + CompanyID = 10, + CompanyName = "5 Guys" + }; + + var jsonString = "{\u0022CompanyID\u0022:10,\u0022CompanyName\u0022:\u00225 Guys\u0022}"; + + + JsonTestCompany deserializedResult = (JsonTestCompany)JsonConvert.DeserializeObject(jsonString, typeof(JsonTestCompany)); + + Assert.NotNull(deserializedResult); + Assert.Equal("5 Guys", deserializedResult.CompanyName); + Assert.Equal(10, deserializedResult.CompanyID); + + OutputHelper.WriteLine("Finished test..."); + } + [TestMethod] public void Can_serialize_and_deserialize_arrays_of_class_objects_when_array_items_may_be_null() { diff --git a/nanoFramework.Json/JsonConvert.cs b/nanoFramework.Json/JsonConvert.cs index 34722f6..25c1294 100644 --- a/nanoFramework.Json/JsonConvert.cs +++ b/nanoFramework.Json/JsonConvert.cs @@ -33,7 +33,10 @@ private struct LexToken /// /// Convert an object to a JSON string. /// - /// The value to convert. Supported types are: Boolean, String, Byte, (U)Int16, (U)Int32, Float, Double, Decimal, Array, IDictionary, IEnumerable, Guid, Datetime, DictionaryEntry, Object and null. + /// + /// The value to convert. Supported types are: Boolean, String, Byte, (U)Int16, (U)Int32, + /// Float, Double, Decimal, Array, IDictionary, IEnumerable, Guid, Datetime, DictionaryEntry, Object and null. + /// /// The JSON object as a string or null when the value type is not supported. /// For objects, only public properties with getters are converted. public static string SerializeObject(object oSource) @@ -1088,7 +1091,7 @@ private static LexToken GetNextTokenInternal(ref int jsonPos, ref byte[] jsonByt return EndToken(sb); } - //TODO: replace with a mapping array? This switch is really incomplete. + // handle escaped characters switch (ch) { case 't': @@ -1115,15 +1118,21 @@ private static LexToken GetNextTokenInternal(ref int jsonPos, ref byte[] jsonByt ch = '\\'; break; - case 'u': - unicodeEncoded = true; - break; - case '"': ch = '"'; break; + case '\'': + ch = '\''; + break; + + case 'u': + // unicode escape \uXXXX - we'll handle below + unicodeEncoded = true; + break; + default: + // unknown escape sequence throw new DeserializationException(); } } @@ -1132,56 +1141,32 @@ private static LexToken GetNextTokenInternal(ref int jsonPos, ref byte[] jsonByt { if (unicodeEncoded) { - int numberCounter = 0; - - // next 4 chars have to be numeric - StringBuilder encodedValue = new(); - - // advance position to next char - jsonPos++; - ch = (char)jsonBytes[jsonPos]; - - for (int i = 0; i < 4; i++) + // We must decode exactly 4 hex digits following the '\u' + // Ensure there are at least 4 bytes remaining + if (jsonPos + 4 > jsonBytes.Length) { - if (IsNumberChar(ch)) - { - numberCounter++; - - encodedValue.Append(ch); - - ch = (char)jsonBytes[jsonPos]; - - if (IsNumberChar(ch)) - { - // We're still working on the number - advance jsonPos - jsonPos++; - } - } + throw new DeserializationException(); } - if (numberCounter == 4) + // Read 4 ASCII hex chars (they should be ASCII hex digits) + int val = 0; + for (int i = 0; i < 4; i++) { - // we're good with the encoded data - // try parse number as an UTF-8 char - try - { - // NOTE: the encoded value has hexadecimal format - int unicodeChar = Convert.ToInt16(encodedValue.ToString(), 16); - - _ = sb.Append((char)unicodeChar); - } - catch + char hc = (char)jsonBytes[jsonPos++]; + int digit; + if (hc >= '0' && hc <= '9') digit = hc - '0'; + else if (hc >= 'a' && hc <= 'f') digit = 10 + (hc - 'a'); + else if (hc >= 'A' && hc <= 'F') digit = 10 + (hc - 'A'); + else { - // couldn't parse this number as a valid Unicode value throw new DeserializationException(); } + + val = (val << 4) + digit; } - else - { - // anything else, we can't parse it properly - // throw exception - throw new DeserializationException(); - } + + // Append the decoded unicode character + sb.Append((char)val); } else {