Skip to content

Commit

Permalink
Merge pull request #930 from WildernessLabs/feature/microjson-dictionary
Browse files Browse the repository at this point in the history
Feature/microjson dictionary
  • Loading branch information
adrianstevens authored Mar 16, 2024
2 parents 304fd50 + 15b6111 commit ceb8da9
Show file tree
Hide file tree
Showing 11 changed files with 309 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,35 @@ public static object Deserialize(string json, Type type)

if (rootArray is not null)
{
foreach (Hashtable item in rootArray)
foreach (var item in rootArray)
{
object instance = Activator.CreateInstance(elementType);
Deserialize(item, elementType, ref instance);
targetArray.SetValue(instance, index++);
if (item is Hashtable h)
{
object instance = Activator.CreateInstance(elementType);
Deserialize(h, elementType, ref instance);
targetArray.SetValue(instance, index++);
}
else
{
var instance = Convert.ChangeType(item, elementType);
targetArray.SetValue(instance, index++);
}
}
}

return targetArray;
}
else if (typeof(IDictionary).IsAssignableFrom(type))
{
if (type.IsGenericType)
{
var table = DeserializeString(json) as Hashtable;

return DeserializeHashtableToDictionary(table, type)
?? throw new NotSupportedException($"Type '{type.Name}' not supported");
}
throw new NotSupportedException($"Type '{type.Name}' not supported");
}
else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
{
var elementType = type.GetGenericArguments()[0];
Expand Down Expand Up @@ -159,6 +178,24 @@ public static object Deserialize(string json, Type type)
}
}

private static IDictionary? DeserializeHashtableToDictionary(Hashtable hashtable, Type dictionaryType)
{
var genericArguments = dictionaryType.GetGenericArguments();
var keyType = genericArguments[0];
var valueType = genericArguments[1];

var dictionary = (IDictionary)Activator.CreateInstance(dictionaryType);

foreach (DictionaryEntry entry in hashtable)
{
object key = Convert.ChangeType(entry.Key, keyType);
object value = Convert.ChangeType(entry.Value, valueType);
dictionary.Add(key, value);
}

return dictionary;
}

/// <summary>
/// Deserializes an object of the specified type from a JSON string.
/// </summary>
Expand Down Expand Up @@ -229,20 +266,8 @@ private static void Deserialize(Hashtable? root, Type type, ref object instance)
}
else if (propType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>)))
{
var dictionaryType = propType.GetGenericTypeDefinition();
var keyType = propType.GetGenericArguments()[0];
var valueType = propType.GetGenericArguments()[1];

var dictionary = (IDictionary)Activator.CreateInstance(propType);

foreach (DictionaryEntry entry in (Hashtable)values[v])
{
object key = Convert.ChangeType(entry.Key, keyType);
object value = Activator.CreateInstance(valueType);
Deserialize((Hashtable)entry.Value, valueType, ref value);
dictionary.Add(key, value);
}

var dictionary = DeserializeHashtableToDictionary((Hashtable)values[v], propType)
?? throw new NotSupportedException($"Type '{type.Name}' not supported");
prop.SetValue(instance, dictionary);
}
else if (IsComplexType(propType))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public static partial class MicroJson
/// <param name="dateTimeFormat">The format to use for DateTime values. Defaults to ISO 8601 format.</param>
/// <returns>The JSON object as a string or null when the value type is not supported.</returns>
/// <remarks>For objects, only public properties with getters are converted.</remarks>
public static string? Serialize(object o, DateTimeFormat dateTimeFormat = DateTimeFormat.ISO8601)
public static string? Serialize(object o, DateTimeFormat dateTimeFormat = DateTimeFormat.ISO8601, bool convertNamesToCamelCase = true)
{
if (o == null)
{
Expand Down Expand Up @@ -103,9 +103,9 @@ public static partial class MicroJson
if (o is DictionaryEntry entry)
{
var hashtable = new Hashtable
{
{ entry.Key, entry.Value }
};
{
{ entry.Key, entry.Value }
};
return SerializeIDictionary(hashtable, dateTimeFormat);
}

Expand All @@ -121,7 +121,12 @@ public static partial class MicroJson
foreach (PropertyInfo property in properties)
{
object returnObject = property.GetValue(o);
hashtable.Add(property.Name, returnObject);
var name = convertNamesToCamelCase
? char.ToLowerInvariant(property.Name[0]) + property.Name[1..]
: property.Name;

// camel case the name
hashtable.Add(name, returnObject);
}

return SerializeIDictionary(hashtable, dateTimeFormat);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,40 @@ public void SimpleIntegerPropertyTest()

Assert.Equal(23, result.Value);
}

[Fact]
public void SimpleStringArrayTest()
{
var input = """
[
"Value1",
"Value2",
"Value3"
]
""";

var result = MicroJson.Deserialize<string[]>(input);

Assert.Equal(3, result.Length);
}

[Fact]
public void SerializeToCamelCaseTest()
{
var item = new IntegerClass { Value = 23 };
var json = MicroJson.Serialize(item);

Assert.Contains("value", json);
Assert.DoesNotContain("Value", json);
}

[Fact]
public void SerializeToNonCamelCaseTest()
{
var item = new IntegerClass { Value = 23 };
var json = MicroJson.Serialize(item, convertNamesToCamelCase: false);

Assert.Contains("Value", json);
Assert.DoesNotContain("value", json);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using Meadow.Cloud;
using Meadow.Foundation.Serialization;
using Meadow.Update;
using System.Collections.Generic;
using Xunit;

namespace Unit.Tests;

public class CloudEntityTests
{
[Fact]
public void UpdateMessageSerializationTest()
{
UpdateMessage message = new()
{

};

var json = MicroJson.Serialize(message);
}

[Fact]
public void MeadowCommandSerializationTest()
{
var command = new MeadowCommand("command name",
new Dictionary<string, object>
{
{ "field 1", 23 },
{ "field 2", "foo" },
{ "field 3", true },
{ "field 4", 42.2d }
});

var json = MicroJson.Serialize(command);
}

[Fact]
public void MeadowCommandDeserializationTest()
{
var expected = new Dictionary<string, object>
{
{ "field 1", 23L },
{ "field 2", "foo" },
{ "field 3", true },
{ "field 4", 42.2d }
};

var json = "{\"field 1\":23,\"field 2\":\"foo\",\"field 3\":true,\"field 4\":42.2}";
var result = MicroJson.Deserialize<Dictionary<string, object>>(json);

Assert.NotNull(result);
Assert.Equal(4, result.Count);
foreach (var kvp in expected)
{
Assert.True(result.ContainsKey(kvp.Key));
// this fails because the boxed '23' values arent-'t equat
// Assert.True(result[kvp.Key] == kvp.Value, $"{result[kvp.Key]} != {kvp.Value}");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Meadow.Foundation.Serialization;
using Xunit;

namespace Unit.Tests;

public class MenuJsonTests
{
[Fact]
public void DeserializeMenuTest()
{
var json = Inputs.GetInputResource("menu.json");
var result = MicroJson.Deserialize<MenuContainer>(json);

Assert.NotNull(result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\..\..\Meadow.Contracts\Source\Meadow.Contracts\Meadow.Contracts.csproj" />
<ProjectReference Include="..\..\Driver\MicroJson.csproj" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Meadow.Foundation.Serialization;
using System.Collections.Generic;
using Xunit;

namespace Unit.Tests;

public class PuzzleJsonTests
{
[Fact]
public void DeserializePuzzlesAsArrayTest()
{
var json = Inputs.GetInputResource("puzzles.json");
var result = MicroJson.Deserialize<Puzzle[]>(json);

Assert.NotNull(result);
Assert.Equal(10, result.Length);

foreach (var puzzle in result)
{
Assert.NotNull(puzzle);
Assert.NotNull(puzzle.Pieces);

foreach (var piece in puzzle.Pieces)
{
Assert.NotNull(piece);
}
}
}

[Fact]
public void DeserializePuzzlesAsListTest()
{
var json = Inputs.GetInputResource("puzzles.json");
var result = MicroJson.Deserialize<List<Puzzle>>(json);

Assert.NotNull(result);
Assert.Equal(10, result.Count);

foreach (var puzzle in result)
{
Assert.NotNull(puzzle);
Assert.NotNull(puzzle.Pieces);

foreach (var piece in puzzle.Pieces)
{
Assert.NotNull(piece);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Unit.Tests;

internal class MenuContainer
{
public MenuItem[] Menu { get; set; }
}

internal class MenuItem
{
public string Text { get; set; }
public string Id { get; set; }
public string Type { get; set; }
public int Value { get; set; }
public MenuItem[] Sub { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Unit.Tests;

public enum PieceType
{
Horizontal2,
Horizontal3,
Vertical2,
Vertical3,
Solve
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Collections.Generic;

namespace Unit.Tests;

public class Puzzle
{
public List<PuzzlePiece> Pieces { get; set; }

public int MinMoves { get; set; } = -1;
public int NumBlocks => Pieces.Count;

public Puzzle()
{
Pieces = [];
}

public bool AddPiece(int x, int y, PieceType type)
{
PuzzlePiece piece = new PuzzlePiece(x, y, type);

Pieces.Add(piece);

return true;
}
}
Loading

0 comments on commit ceb8da9

Please sign in to comment.