// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; namespace Microsoft.ReactNative.Managed.UnitTests { class RobotInfo { public RobotModel Model { get; set; } public string Name { get; set; } public int Age; public RobotShape Shape { get; set; } public RobotShape? Shape2 { get; set; } public RobotShape? Shape3 { get; set; } public IList Steps { get; set; } public IDictionary Dimensions { get; set; } public Tuple Badges { get; set; } public RobotTool[] Tools { get; set; } public RobotPoint[] Path { get; set; } public OneOf2 Extra { get; set; } } enum RobotModel { T2, R2D2, C3PO, }; // We are generating ReadValue for it. enum RobotShape { Humanoid, Trashcan, Beercan, Quadrocopter, } class RobotTool { public string Name { get; set; } public int Weight = 0; public bool IsEnabled { get; set; } } struct RobotPoint { public int X; public int Y; } class T2Extra { public string ActorName { get; set; } public int MovieYear { get; set; } } class R2D2Extra { public string MovieSeries { get; set; } } static class RobotSerialization { // Reading RobotInfo value. It could be generated instead. public static void ReadValue(this IJSValueReader reader, out RobotInfo value) { value = new RobotInfo(); if (reader.ValueType == JSValueType.Object) { while (reader.GetNextObjectProperty(out string propertyName)) { switch (propertyName) { case nameof(value.Model): value.Model = reader.ReadValue(); break; case nameof(value.Name): value.Name = reader.ReadValue(); break; case nameof(value.Age): value.Age = reader.ReadValue(); break; case nameof(value.Shape): value.Shape = reader.ReadValue(); break; case nameof(value.Shape2): value.Shape2 = reader.ReadValue(); break; case nameof(value.Shape3): value.Shape3 = reader.ReadValue(); break; case nameof(value.Steps): value.Steps = reader.ReadValue>(); break; case nameof(value.Dimensions): value.Dimensions = reader.ReadValue>(); break; case nameof(value.Badges): value.Badges = reader.ReadValue>(); break; case nameof(value.Tools): value.Tools = reader.ReadValue(); break; case nameof(value.Path): value.Path = reader.ReadValue(); break; case nameof(value.Extra): value.Extra = reader.ReadValue>(); break; default: reader.ReadValue(); break; } } } } // Writing RobotInfo value. It could be generated instead. public static void WriteValue(this IJSValueWriter writer, RobotInfo value) { if (value != null) { writer.WriteObjectBegin(); writer.WriteObjectProperty(nameof(value.Model), value.Model); writer.WriteObjectProperty(nameof(value.Name), value.Name); writer.WriteObjectProperty(nameof(value.Age), value.Age); writer.WriteObjectProperty(nameof(value.Shape), value.Shape); writer.WriteObjectProperty(nameof(value.Shape2), value.Shape2); writer.WriteObjectProperty(nameof(value.Shape3), value.Shape3); writer.WriteObjectProperty(nameof(value.Steps), value.Steps); writer.WriteObjectProperty(nameof(value.Dimensions), value.Dimensions); writer.WriteObjectProperty(nameof(value.Badges), value.Badges); writer.WriteObjectProperty(nameof(value.Tools), value.Tools); writer.WriteObjectProperty(nameof(value.Path), value.Path); writer.WriteObjectProperty(nameof(value.Extra), value.Extra); writer.WriteObjectEnd(); } else { writer.WriteNull(); } } // Reading RobotModel enum value. It could be generated instead. public static void ReadValue(this IJSValueReader reader, out RobotModel value) { value = (RobotModel)reader.ReadValue(); } // Writing RobotModel enum value. It could be generated instead. public static void WriteValue(this IJSValueWriter writer, RobotModel value) { writer.WriteValue((int)value); } // Reading discriminating union requires using JSValue. public static void ReadValue(this JSValue jsValue, out OneOf2 value) { value = default(OneOf2); if (jsValue.TryGetObjectProperty("Kind", out JSValue kind)) { RobotModel modelType = kind.ReadValue(); switch (modelType) { case RobotModel.T2: value = jsValue.ReadValue(); break; case RobotModel.R2D2: value = jsValue.ReadValue(); break; } } } // Writing discriminating union is simpler than reading. public static void WriteValue(this IJSValueWriter writer, OneOf2 value) { writer.WriteObjectBegin(); if (value.TryGet(out T2Extra t2)) { writer.WriteObjectProperty("Kind", RobotModel.T2); writer.WriteObjectProperties(t2); } else if (value.TryGet(out R2D2Extra r2d2)) { writer.WriteObjectProperty("Kind", RobotModel.R2D2); writer.WriteObjectProperties(r2d2); } writer.WriteObjectEnd(); } } [TestClass] public class JSValueReaderTest { public JSValueReaderTest() { var assembly = typeof(RobotSerialization).Assembly; JSValueReaderGenerator.RegisterAssembly(assembly); JSValueWriterGenerator.RegisterAssembly(assembly); } [TestMethod] public void TestReadCustomType() { JObject jobj = JObject.Parse(@"{ Model: 1, Name: ""Bob"", Age: 42, Shape: 1, Shape2: 2, Shape3: null, Steps: [1, 2, 3], Dimensions: {Width: 24, Height: 78}, Badges: [2, ""Maverick"", true], Tools: [{Name: ""Screwdriver"", Weight: 2, IsEnabled: true}, {Name: ""Electro-shocker"", Weight: 3, IsEnabled: false}], Path: [{X: 5, Y: 6}, {X: 45, Y: 90}, {X: 15, Y: 16}], Extra: {Kind: 1, MovieSeries: ""Episode 2""} }"); IJSValueReader reader = new JTokenJSValueReader(jobj); reader.ReadValue(out RobotInfo robot); Assert.AreEqual(RobotModel.R2D2, robot.Model); Assert.AreEqual("Bob", robot.Name); Assert.AreEqual(42, robot.Age); Assert.AreEqual(RobotShape.Trashcan, robot.Shape); Assert.AreEqual(RobotShape.Beercan, robot.Shape2.Value); Assert.IsFalse(robot.Shape3.HasValue); Assert.AreEqual(3, robot.Steps.Count); Assert.AreEqual(1, robot.Steps[0]); Assert.AreEqual(2, robot.Steps[1]); Assert.AreEqual(3, robot.Steps[2]); Assert.AreEqual(2, robot.Dimensions.Count); Assert.AreEqual(24, robot.Dimensions["Width"]); Assert.AreEqual(78, robot.Dimensions["Height"]); Assert.AreEqual(2, robot.Badges.Item1); Assert.AreEqual("Maverick", robot.Badges.Item2); Assert.AreEqual(true, robot.Badges.Item3); Assert.AreEqual(2, robot.Tools.Length); Assert.AreEqual("Screwdriver", robot.Tools[0].Name); Assert.AreEqual(2, robot.Tools[0].Weight); Assert.AreEqual(true, robot.Tools[0].IsEnabled); Assert.AreEqual("Electro-shocker", robot.Tools[1].Name); Assert.AreEqual(3, robot.Tools[1].Weight); Assert.AreEqual(false, robot.Tools[1].IsEnabled); Assert.AreEqual(3, robot.Path.Length); Assert.AreEqual(5, robot.Path[0].X); Assert.AreEqual(6, robot.Path[0].Y); Assert.AreEqual(45, robot.Path[1].X); Assert.AreEqual(90, robot.Path[1].Y); Assert.AreEqual(15, robot.Path[2].X); Assert.AreEqual(16, robot.Path[2].Y); Assert.AreEqual(true, robot.Extra.TryGet(out var r2d2Extra)); Assert.AreEqual("Episode 2", r2d2Extra.MovieSeries); } [TestMethod] public void TestWriteCustomType() { var robot = new RobotInfo { Model = RobotModel.R2D2, Name = "Bob", Age = 42, Shape = RobotShape.Trashcan, Shape2 = RobotShape.Beercan, Shape3 = null, Steps = new List { 1, 2, 3 }, Dimensions = new Dictionary { ["Width"] = 24, ["Height"] = 78 }, Badges = Tuple.Create(2, "Maverick", true), Tools = new RobotTool[] { new RobotTool { Name = "Screwdriver", Weight = 2, IsEnabled = true}, new RobotTool { Name = "Electro-shocker", Weight = 3, IsEnabled = false} }, Path = new RobotPoint[] { new RobotPoint {X = 5, Y = 6}, new RobotPoint { X = 45, Y = 90}, new RobotPoint { X = 15, Y = 16} }, Extra = new R2D2Extra { MovieSeries = "Episode 2" } }; var writer = new JTokenJSValueWriter(); writer.WriteValue(robot); JToken jValue = writer.TakeValue(); Assert.AreEqual((int)RobotModel.R2D2, jValue["Model"]); Assert.AreEqual("Bob", jValue["Name"]); Assert.AreEqual(42, jValue["Age"]); Assert.AreEqual((int)RobotShape.Trashcan, jValue["Shape"]); Assert.AreEqual((int)RobotShape.Beercan, jValue["Shape2"]); Assert.AreEqual(JTokenType.Null, jValue["Shape3"].Type); Assert.AreEqual(3, (jValue["Steps"] as JArray).Count); Assert.AreEqual(1, jValue["Steps"][0]); Assert.AreEqual(2, jValue["Steps"][1]); Assert.AreEqual(3, jValue["Steps"][2]); Assert.AreEqual(2, (jValue["Dimensions"] as JObject).Count); Assert.AreEqual(24, jValue["Dimensions"]["Width"]); Assert.AreEqual(78, jValue["Dimensions"]["Height"]); Assert.AreEqual(2, jValue["Badges"][0]); Assert.AreEqual("Maverick", jValue["Badges"][1]); Assert.AreEqual(true, jValue["Badges"][2]); Assert.AreEqual(2, (jValue["Tools"] as JArray).Count); Assert.AreEqual("Screwdriver", jValue["Tools"][0]["Name"]); Assert.AreEqual(2, jValue["Tools"][0]["Weight"]); Assert.AreEqual(true, jValue["Tools"][0]["IsEnabled"]); Assert.AreEqual("Electro-shocker", jValue["Tools"][1]["Name"]); Assert.AreEqual(3, jValue["Tools"][1]["Weight"]); Assert.AreEqual(false, jValue["Tools"][1]["IsEnabled"]); Assert.AreEqual(3, (jValue["Path"] as JArray).Count); Assert.AreEqual(5, jValue["Path"][0]["X"]); Assert.AreEqual(6, jValue["Path"][0]["Y"]); Assert.AreEqual(45, jValue["Path"][1]["X"]); Assert.AreEqual(90, jValue["Path"][1]["Y"]); Assert.AreEqual(15, jValue["Path"][2]["X"]); Assert.AreEqual(16, jValue["Path"][2]["Y"]); Assert.AreEqual(1, jValue["Extra"]["Kind"]); Assert.AreEqual("Episode 2", jValue["Extra"]["MovieSeries"]); } [TestMethod] public void TestReadValueDefaultExtensions() { JObject jobj = JObject.Parse(@"{ StringValue1: """", StringValue2: ""5"", StringValue3: ""Hello"", BoolValue1: false, BoolValue2: true, IntValue1: 0, IntValue2: 42, FloatValue: 3.14, NullValue: null }"); IJSValueReader reader = new JTokenJSValueReader(jobj); Assert.AreEqual(JSValueType.Object, reader.ValueType); int properyCount = 0; while (reader.GetNextObjectProperty(out string propertyName)) { switch (propertyName) { case "StringValue1": Assert.AreEqual("", reader.ReadValue()); Assert.AreEqual(false, reader.ReadValue()); Assert.AreEqual((sbyte)0, reader.ReadValue()); Assert.AreEqual((short)0, reader.ReadValue()); Assert.AreEqual(0, reader.ReadValue()); Assert.AreEqual(0, reader.ReadValue()); Assert.AreEqual((byte)0, reader.ReadValue()); Assert.AreEqual((ushort)0, reader.ReadValue()); Assert.AreEqual((uint)0, reader.ReadValue()); Assert.AreEqual((ulong)0, reader.ReadValue()); Assert.AreEqual(0, reader.ReadValue()); Assert.AreEqual(0, reader.ReadValue()); break; case "StringValue2": Assert.AreEqual("5", reader.ReadValue()); Assert.AreEqual(true, reader.ReadValue()); Assert.AreEqual((sbyte)5, reader.ReadValue()); Assert.AreEqual((short)5, reader.ReadValue()); Assert.AreEqual(5, reader.ReadValue()); Assert.AreEqual(5, reader.ReadValue()); Assert.AreEqual((byte)5, reader.ReadValue()); Assert.AreEqual((ushort)5, reader.ReadValue()); Assert.AreEqual((uint)5, reader.ReadValue()); Assert.AreEqual((ulong)5, reader.ReadValue()); Assert.AreEqual(5, reader.ReadValue()); Assert.AreEqual(5, reader.ReadValue()); break; case "StringValue3": Assert.AreEqual("Hello", reader.ReadValue()); Assert.AreEqual(true, reader.ReadValue()); Assert.AreEqual((sbyte)0, reader.ReadValue()); Assert.AreEqual((short)0, reader.ReadValue()); Assert.AreEqual(0, reader.ReadValue()); Assert.AreEqual(0, reader.ReadValue()); Assert.AreEqual((byte)0, reader.ReadValue()); Assert.AreEqual((ushort)0, reader.ReadValue()); Assert.AreEqual((uint)0, reader.ReadValue()); Assert.AreEqual((ulong)0, reader.ReadValue()); Assert.AreEqual(0, reader.ReadValue()); Assert.AreEqual(0, reader.ReadValue()); break; case "BoolValue1": Assert.AreEqual("false", reader.ReadValue()); Assert.AreEqual(false, reader.ReadValue()); Assert.AreEqual((sbyte)0, reader.ReadValue()); Assert.AreEqual((short)0, reader.ReadValue()); Assert.AreEqual(0, reader.ReadValue()); Assert.AreEqual(0, reader.ReadValue()); Assert.AreEqual((byte)0, reader.ReadValue()); Assert.AreEqual((ushort)0, reader.ReadValue()); Assert.AreEqual((uint)0, reader.ReadValue()); Assert.AreEqual((ulong)0, reader.ReadValue()); Assert.AreEqual(0, reader.ReadValue()); Assert.AreEqual(0, reader.ReadValue()); break; case "BoolValue2": Assert.AreEqual("true", reader.ReadValue()); Assert.AreEqual(true, reader.ReadValue()); Assert.AreEqual((sbyte)1, reader.ReadValue()); Assert.AreEqual((short)1, reader.ReadValue()); Assert.AreEqual(1, reader.ReadValue()); Assert.AreEqual(1, reader.ReadValue()); Assert.AreEqual((byte)1, reader.ReadValue()); Assert.AreEqual((ushort)1, reader.ReadValue()); Assert.AreEqual((uint)1, reader.ReadValue()); Assert.AreEqual((ulong)1, reader.ReadValue()); Assert.AreEqual(1, reader.ReadValue()); Assert.AreEqual(1, reader.ReadValue()); break; case "IntValue1": Assert.AreEqual("0", reader.ReadValue()); Assert.AreEqual(false, reader.ReadValue()); Assert.AreEqual((sbyte)0, reader.ReadValue()); Assert.AreEqual((short)0, reader.ReadValue()); Assert.AreEqual(0, reader.ReadValue()); Assert.AreEqual(0, reader.ReadValue()); Assert.AreEqual((byte)0, reader.ReadValue()); Assert.AreEqual((ushort)0, reader.ReadValue()); Assert.AreEqual((uint)0, reader.ReadValue()); Assert.AreEqual((ulong)0, reader.ReadValue()); Assert.AreEqual(0, reader.ReadValue()); Assert.AreEqual(0, reader.ReadValue()); break; case "IntValue2": Assert.AreEqual("42", reader.ReadValue()); Assert.AreEqual(true, reader.ReadValue()); Assert.AreEqual((sbyte)42, reader.ReadValue()); Assert.AreEqual((short)42, reader.ReadValue()); Assert.AreEqual(42, reader.ReadValue()); Assert.AreEqual(42, reader.ReadValue()); Assert.AreEqual((byte)42, reader.ReadValue()); Assert.AreEqual((ushort)42, reader.ReadValue()); Assert.AreEqual((uint)42, reader.ReadValue()); Assert.AreEqual((ulong)42, reader.ReadValue()); Assert.AreEqual(42, reader.ReadValue()); Assert.AreEqual(42, reader.ReadValue()); break; case "FloatValue": Assert.AreEqual("3.14", reader.ReadValue()); Assert.AreEqual(true, reader.ReadValue()); Assert.AreEqual((sbyte)3, reader.ReadValue()); Assert.AreEqual((short)3, reader.ReadValue()); Assert.AreEqual(3, reader.ReadValue()); Assert.AreEqual(3, reader.ReadValue()); Assert.AreEqual((byte)3, reader.ReadValue()); Assert.AreEqual((ushort)3, reader.ReadValue()); Assert.AreEqual((uint)3, reader.ReadValue()); Assert.AreEqual((ulong)3, reader.ReadValue()); Assert.AreEqual((float)3.14, reader.ReadValue()); Assert.AreEqual(3.14, reader.ReadValue()); break; case "NullValue": Assert.AreEqual("", reader.ReadValue()); Assert.AreEqual(false, reader.ReadValue()); Assert.AreEqual((sbyte)0, reader.ReadValue()); Assert.AreEqual((short)0, reader.ReadValue()); Assert.AreEqual(0, reader.ReadValue()); Assert.AreEqual(0, reader.ReadValue()); Assert.AreEqual((byte)0, reader.ReadValue()); Assert.AreEqual((ushort)0, reader.ReadValue()); Assert.AreEqual((uint)0, reader.ReadValue()); Assert.AreEqual((ulong)0, reader.ReadValue()); Assert.AreEqual(0, reader.ReadValue()); Assert.AreEqual(0, reader.ReadValue()); break; } ++properyCount; } Assert.AreEqual(9, properyCount); } [TestMethod] public void TestWriteValueDefaultExtensions() { var writer = new JTokenJSValueWriter(); writer.WriteObjectBegin(); writer.WriteObjectProperty("StringValue1", ""); writer.WriteObjectProperty("StringValue2", "5"); writer.WriteObjectProperty("StringValue3", "Hello"); writer.WriteObjectProperty("BoolValue1", false); writer.WriteObjectProperty("BoolValue2", true); writer.WriteObjectProperty("IntValue1", 0); writer.WriteObjectProperty("IntValue2", 42); writer.WriteObjectProperty("FloatValue", 3.14); writer.WriteObjectProperty("NullValue", JSValue.Null); writer.WriteObjectEnd(); JToken jValue = writer.TakeValue(); Assert.AreEqual("", jValue["StringValue1"]); Assert.AreEqual("5", jValue["StringValue2"]); Assert.AreEqual("Hello", jValue["StringValue3"]); Assert.AreEqual(false, jValue["BoolValue1"]); Assert.AreEqual(true, jValue["BoolValue2"]); Assert.AreEqual(0, jValue["IntValue1"]); Assert.AreEqual(42, jValue["IntValue2"]); Assert.AreEqual(3.14, jValue["FloatValue"]); Assert.AreEqual(JTokenType.Null, jValue["NullValue"].Type); } } }