Skip to content

Commit fa653fb

Browse files
committed
Serialize list raw results into properties/fields of user passed types
1 parent 889fbc5 commit fa653fb

File tree

1 file changed

+125
-23
lines changed

1 file changed

+125
-23
lines changed

Assets/Thirdweb/Core/Scripts/Contract.cs

+125-23
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
using UnityEngine;
44
using Nethereum.Hex.HexTypes;
55
using Nethereum.Web3;
6+
using Newtonsoft.Json;
7+
using System.Collections.Generic;
8+
using Nethereum.ABI.FunctionEncoding;
9+
using System.Linq;
10+
using System;
11+
using System.Collections;
612

713
namespace Thirdweb
814
{
@@ -75,29 +81,6 @@ public async Task<CurrencyValue> GetBalance()
7581
}
7682
}
7783

78-
/// <summary>
79-
/// Read data from a contract
80-
/// </summary>
81-
/// <param name="functionName">The contract function name to call</param>
82-
/// <param name="args">Optional function arguments. Structs and Lists will get serialized automatically</param>
83-
/// <returns>The data deserialized to the given typed</returns>
84-
public async Task<T> Read<T>(string functionName, params object[] args)
85-
{
86-
if (Utils.IsWebGLBuild())
87-
{
88-
return await Bridge.InvokeRoute<T>(getRoute("call"), Utils.ToJsonStringArray(functionName, args));
89-
}
90-
else
91-
{
92-
if (this.abi == null)
93-
throw new UnityException("You must pass an ABI for native platform custom calls");
94-
95-
var contract = new Web3(ThirdwebManager.Instance.SDK.session.RPC).Eth.GetContract(this.abi, this.address);
96-
var function = contract.GetFunction(functionName);
97-
return await function.CallAsync<T>(args);
98-
}
99-
}
100-
10184
/// <summary>
10285
/// Execute a write transaction on a contract
10386
/// </summary>
@@ -149,5 +132,124 @@ public async Task<TransactionResult> Write(string functionName, TransactionReque
149132
return receipt.ToTransactionResult();
150133
}
151134
}
135+
136+
/// <summary>
137+
/// Read data from a contract
138+
/// </summary>
139+
/// <param name="functionName">The contract function name to call</param>
140+
/// <param name="args">Optional function arguments. Structs and Lists will get serialized automatically</param>
141+
/// <returns>The data deserialized to the given typed</returns>
142+
public async Task<T> Read<T>(string functionName, params object[] args)
143+
{
144+
if (Utils.IsWebGLBuild())
145+
{
146+
return await Bridge.InvokeRoute<T>(getRoute("call"), Utils.ToJsonStringArray(functionName, args));
147+
}
148+
149+
if (this.abi == null)
150+
throw new UnityException("You must pass an ABI for native platform custom calls");
151+
152+
var contract = new Web3(ThirdwebManager.Instance.SDK.session.RPC).Eth.GetContract(this.abi, this.address);
153+
var function = contract.GetFunction(functionName);
154+
var result = await function.CallDecodingToDefaultAsync(args);
155+
156+
var rawResults = new List<object>();
157+
158+
if (result[0].Result is List<ParameterOutput> parameterOutputs)
159+
rawResults.AddRange(parameterOutputs.Select(item => item.Result));
160+
else
161+
rawResults.AddRange(result.Select(item => item.Result));
162+
163+
Debug.Log("Raw Result: " + JsonConvert.SerializeObject(rawResults));
164+
165+
// Single
166+
if (rawResults.Count == 1)
167+
{
168+
return ConvertValue<T>(rawResults[0]);
169+
}
170+
171+
// List or array
172+
if ((typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(List<>)) || (typeof(T).IsArray))
173+
{
174+
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(rawResults));
175+
}
176+
177+
// Class or struct
178+
if (typeof(T).IsClass || typeof(T).IsValueType)
179+
{
180+
var targetType = typeof(T);
181+
var properties = targetType.GetProperties();
182+
var fields = targetType.GetFields();
183+
184+
if (rawResults.Count == properties.Length)
185+
{
186+
var instance = Activator.CreateInstance<T>();
187+
188+
for (int i = 0; i < properties.Length; i++)
189+
{
190+
try
191+
{
192+
properties[i].SetValue(instance, rawResults[i]);
193+
}
194+
catch (ArgumentException ex)
195+
{
196+
throw new UnityException(
197+
$"Type mismatch assigning value to property {properties[i].Name}: expected {rawResults[i].GetType().Name}, got {properties[i].PropertyType.Name}",
198+
ex
199+
);
200+
}
201+
}
202+
203+
return instance;
204+
}
205+
else if (rawResults.Count == fields.Length)
206+
{
207+
var instance = Activator.CreateInstance<T>();
208+
209+
for (int i = 0; i < fields.Length; i++)
210+
{
211+
try
212+
{
213+
fields[i].SetValue(instance, rawResults[i]);
214+
}
215+
catch (ArgumentException ex)
216+
{
217+
throw new UnityException($"Type mismatch assigning value to field {fields[i].Name}: expected {rawResults[i].GetType().Name}, got {fields[i].FieldType.Name}", ex);
218+
}
219+
}
220+
221+
return instance;
222+
}
223+
else
224+
{
225+
throw new UnityException(
226+
$"The number of properties or fields in the target type do not match the number of results: expected {rawResults.Count}, got {properties.Length} properties and {fields.Length} fields"
227+
);
228+
}
229+
}
230+
231+
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(rawResults));
232+
}
233+
234+
private T ConvertValue<T>(object value)
235+
{
236+
if (value is T result)
237+
{
238+
return result;
239+
}
240+
241+
if (value == null)
242+
{
243+
return default;
244+
}
245+
246+
var targetType = typeof(T);
247+
if (targetType.IsValueType && System.Nullable.GetUnderlyingType(targetType) == null)
248+
{
249+
return (T)System.Convert.ChangeType(value, targetType);
250+
}
251+
252+
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(value));
253+
}
152254
}
153255
}

0 commit comments

Comments
 (0)