Skip to content

Commit

Permalink
Fix JSON serialization for string object and escaped multiline charac…
Browse files Browse the repository at this point in the history
…ters (#226)
  • Loading branch information
torbacz authored Aug 4, 2022
1 parent ca90b41 commit 5f5412a
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 8 deletions.
44 changes: 44 additions & 0 deletions nanoFramework.Json.Test/JsonUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1535,6 +1535,50 @@ public void SerializeCosmosDbObject_02()
Assert.Equal(result.Databases[0]._ts, 1644173816, $"Database._ts is wrong, got {result.Databases[0]._ts}");
}

[TestMethod]
public void SerializeStringWithNewLine_Should_ReturnWithoutNewLine()
{
var inputData = "multiline\nstring";
var expectedJson = "\"multiline\\nstring\"";

var json = JsonConvert.SerializeObject(inputData);

Assert.Equal(expectedJson, json);
}

[TestMethod]
public void SerializeStringWithReturn_Should_ReturnWithoutNewLine()
{
var inputData = "multiline\rstring";
var expectedJson = "\"multiline\\rstring\"";

var json = JsonConvert.SerializeObject(inputData);

Assert.Equal(expectedJson, json);
}

[TestMethod]
public void DeserializeStringWithNewLine_Should_ReturnWithoutNewLine()
{
var inputData = "\"multiline\\nstring\"";
var expectedValue = "multiline\nstring";

var result = (string)JsonConvert.DeserializeObject(inputData, typeof(string));

Assert.Equal(expectedValue, result);
}

[TestMethod]
public void DeserializeStringWithReturn_Should_ReturnWithoutNewLine()
{
var inputData = "\"multiline\\rstring\"";
var expectedValue = "multiline\rstring";

var result = (string)JsonConvert.DeserializeObject(inputData, typeof(string));

Assert.Equal(expectedValue, result);
}

[TestMethod]
public void CanSerializeBoxedEnum()
{
Expand Down
58 changes: 55 additions & 3 deletions nanoFramework.Json/JsonConvert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ internal class SerializationCtx
/// <remarks>For objects, only public properties with getters are converted.</remarks>
public static string SerializeObject(object oSource)
{

return JsonSerializer.SerializeObject(oSource);

}

/// <summary>
Expand All @@ -59,10 +57,64 @@ public static string SerializeObject(object oSource)
/// <returns></returns>
public static object DeserializeObject(string sourceString, Type type)
{
if (type == typeof(string))
{
return DeserializeStringObject(sourceString);
}

var dserResult = Deserialize(sourceString);
return PopulateObject((JsonToken)dserResult, type, "/");
}

private static char GetEscapableCharKeyBasedOnValue(char inputChar)
{
foreach (var item in JsonSerializer.EscapableCharactersMapping.Keys)
{
var value = (char)JsonSerializer.EscapableCharactersMapping[item];
if (value == inputChar)
{
return (char)item;
}
}

// in case inputChar is not supported
throw new InvalidOperationException();
}

private static string DeserializeStringObject(string sourceString)
{
//String by default has escaped \" at beggining and end, just remove them
var resultString = sourceString.Substring(1, sourceString.Length - 2);

if (JsonSerializer.StringContainsCharactersToEscape(resultString, true))
{
var newString = string.Empty;

//Last character can not be escaped, because it's last one
for (int i = 0; i < resultString.Length - 1; i++)
{
var curChar = resultString[i];
var nextChar = resultString[i + 1];

if (curChar == '\\')
{
var charToAppend = GetEscapableCharKeyBasedOnValue(nextChar);
newString += charToAppend;
i++;
continue;
}

newString += curChar;
}

//Append last character skkiped by loop
newString += resultString[resultString.Length - 1];
return newString.ToString();
}

return resultString;
}

#if NANOFRAMEWORK_1_0

/// <summary>
Expand Down Expand Up @@ -1071,7 +1123,7 @@ private static object Deserialize(Stream sourceStream)
return Deserialize();
}

// Deserialize() now assumes that the input has been copied int jsonBytes[]
// Deserialize() now assumes that the input has been copied into jsonBytes[]
// Keep track of position with jsonPos
private static JsonToken Deserialize()
{
Expand Down
45 changes: 40 additions & 5 deletions nanoFramework.Json/JsonSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ namespace nanoFramework.Json
/// </summary>
public class JsonSerializer
{
internal static Hashtable EscapableCharactersMapping = new Hashtable()
{
{'\n', 'n'},
{'\r', 'r'},
{'\"', '"' }
};

/// <summary>
/// Convert an object to a JSON string.
/// </summary>
Expand All @@ -33,8 +40,7 @@ internal static string SerializeObject(object o, bool topObject = true)

if (topObject
&& !type.IsArray
&& (type.BaseType.FullName == "System.ValueType"
|| type.FullName == "System.String"))
&& type.BaseType.FullName == "System.ValueType")
{
return $"[{SerializeObject(o, false)}]";
}
Expand Down Expand Up @@ -205,6 +211,33 @@ internal static string SerializeIDictionary(IDictionary dictionary)
return result;
}

internal static bool StringContainsCharactersToEscape(string str, bool deserializing)
{
foreach (var item in EscapableCharactersMapping.Keys)
{
var charToCheck = deserializing ? $"\\{EscapableCharactersMapping[item]}" : item.ToString();
if (str.IndexOf(charToCheck) > 0)
{
return true;
}
}

return false;
}

internal static bool CheckIfCharIsRequiresEscape(char chr)
{
foreach (var item in EscapableCharactersMapping.Keys)
{
if ((char)item == chr)
{
return true;
}
}

return false;
}

/// <summary>
/// Safely serialize a String into a JSON string value, escaping all backslash and quote characters.
/// </summary>
Expand All @@ -213,7 +246,7 @@ internal static string SerializeIDictionary(IDictionary dictionary)
protected static string SerializeString(string str)
{
// If the string is just fine (most are) then make a quick exit for improved performance
if (str.IndexOf('\\') < 0 && str.IndexOf('\"') < 0)
if (!StringContainsCharactersToEscape(str, false))
{
return str;
}
Expand All @@ -224,12 +257,14 @@ protected static string SerializeString(string str)

foreach (char ch in str)
{
if (ch == '\\' || ch == '\"')
var charToAppend = ch;
if (CheckIfCharIsRequiresEscape(charToAppend))
{
result.Append('\\');
charToAppend = (char)EscapableCharactersMapping[charToAppend];
}

result.Append(ch);
result.Append(charToAppend);
}

return result.ToString();
Expand Down

0 comments on commit 5f5412a

Please sign in to comment.