diff --git a/src/neo/IO/ReferenceEqualityComparer.cs b/src/neo/IO/ReferenceEqualityComparer.cs new file mode 100644 index 0000000000..4c736c1874 --- /dev/null +++ b/src/neo/IO/ReferenceEqualityComparer.cs @@ -0,0 +1,25 @@ +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Neo.IO +{ + internal sealed class ReferenceEqualityComparer : IEqualityComparer, IEqualityComparer + { + public static readonly ReferenceEqualityComparer Default = new ReferenceEqualityComparer(); + + private ReferenceEqualityComparer() + { + } + + public new bool Equals(object x, object y) + { + return x == y; + } + + public int GetHashCode(object obj) + { + return RuntimeHelpers.GetHashCode(obj); + } + } +} diff --git a/src/neo/VM/Helper.cs b/src/neo/VM/Helper.cs index 5ccfe0554e..eaed72fb31 100644 --- a/src/neo/VM/Helper.cs +++ b/src/neo/VM/Helper.cs @@ -1,5 +1,6 @@ using Neo.Cryptography.ECC; using Neo.IO; +using Neo.IO.Json; using Neo.SmartContract; using Neo.VM.Types; using System; @@ -219,6 +220,52 @@ public static byte[] MakeScript(this UInt160 scriptHash, string operation, param } } + public static JObject ToJson(this StackItem item) + { + return ToJson(item, null); + } + + private static JObject ToJson(StackItem item, HashSet context) + { + JObject json = new JObject(); + json["type"] = item.Type; + switch (item) + { + case Array array: + context ??= new HashSet(ReferenceEqualityComparer.Default); + if (!context.Add(array)) throw new InvalidOperationException(); + json["value"] = new JArray(array.Select(p => ToJson(p, context))); + break; + case Boolean boolean: + json["value"] = boolean.ToBoolean(); + break; + case Buffer buffer: + json["value"] = Convert.ToBase64String(buffer.InnerBuffer); + break; + case ByteString byteString: + json["value"] = Convert.ToBase64String(byteString.Span); + break; + case Integer integer: + json["value"] = integer.ToBigInteger().ToString(); + break; + case Map map: + context ??= new HashSet(ReferenceEqualityComparer.Default); + if (!context.Add(map)) throw new InvalidOperationException(); + json["value"] = new JArray(map.Select(p => + { + JObject item = new JObject(); + item["key"] = ToJson(p.Key, context); + item["value"] = ToJson(p.Value, context); + return item; + })); + break; + case Pointer pointer: + json["value"] = pointer.Position; + break; + } + return json; + } + public static ContractParameter ToParameter(this StackItem item) { return ToParameter(item, null); diff --git a/tests/neo.UnitTests/VM/UT_Helper.cs b/tests/neo.UnitTests/VM/UT_Helper.cs index 64e36f194f..498ff433f2 100644 --- a/tests/neo.UnitTests/VM/UT_Helper.cs +++ b/tests/neo.UnitTests/VM/UT_Helper.cs @@ -26,6 +26,27 @@ public void TestEmit() CollectionAssert.AreEqual(new[] { (byte)OpCode.PUSH0 }, sb.ToArray()); } + [TestMethod] + public void TestToJson() + { + var item = new VM.Types.Array(); + item.Add(5); + item.Add("hello world"); + item.Add(new byte[] { 1, 2, 3 }); + item.Add(true); + + Assert.AreEqual("{\"type\":\"Integer\",\"value\":\"5\"}", item[0].ToJson().ToString()); + Assert.AreEqual("{\"type\":\"ByteString\",\"value\":\"aGVsbG8gd29ybGQ=\"}", item[1].ToJson().ToString()); + Assert.AreEqual("{\"type\":\"ByteString\",\"value\":\"AQID\"}", item[2].ToJson().ToString()); + Assert.AreEqual("{\"type\":\"Boolean\",\"value\":true}", item[3].ToJson().ToString()); + Assert.AreEqual("{\"type\":\"Array\",\"value\":[{\"type\":\"Integer\",\"value\":\"5\"},{\"type\":\"ByteString\",\"value\":\"aGVsbG8gd29ybGQ=\"},{\"type\":\"ByteString\",\"value\":\"AQID\"},{\"type\":\"Boolean\",\"value\":true}]}", item.ToJson().ToString()); + + var item2 = new VM.Types.Map(); + item2[1] = new Pointer(new Script(new byte[0]), 0); + + Assert.AreEqual("{\"type\":\"Map\",\"value\":[{\"key\":{\"type\":\"Integer\",\"value\":\"1\"},\"value\":{\"type\":\"Pointer\",\"value\":0}}]}", item2.ToJson().ToString()); + } + [TestMethod] public void TestEmitAppCall1() {