We have a local Web API used for internal clients to retrieve data. We use application/json to transport the data across the Web API.
The retrieved JSON data has a simple structured class 'Response'. This contains a status enum, errorMessage string, errorDetail string and an object. This object always holds a list of requested class objects, being models for our MVVM UIs.
When retrieving many thousand class objects, we found deserialization, using Newtonsoft.Json slow as we had to deserialize into the 'Response' class and also deserialize the Response.Object.
The way we sped up the process was to create a Reflection.Emit.ModuleBuilder class as shown here:
public class ResponseBuilder
{
private static ModuleBuilder _ModuleBuilder;
private static Dictionary<string, Type> _ResponseTypes = new Dictionary<string, Type>();
private static object _lock = new object();
/// <summary>
/// Create a new type 'Response_T' based on Data.Response with Object set to List<T>
/// </summary>
public static Type CreateResponseType<T>()
{
lock (_lock)
{
if (_ModuleBuilder == null)
{
AssemblyName aname = new AssemblyName("ResponseBuilder");
AppDomain currentDomain = AppDomain.CurrentDomain;
AssemblyBuilder abuilder = currentDomain.DefineDynamicAssembly(aname,
AssemblyBuilderAccess.Run);
_ModuleBuilder = abuilder.DefineDynamicModule("ResponseClasses");
}
// Set new type name to 'Response_T'
string newTypeName = "Response_" + typeof(T).FullName;
// Check if this type has already been created and stored in dictionary
if (_ResponseTypes.ContainsKey(newTypeName))
{
return _ResponseTypes[newTypeName];
}
// Create the new type
TypeBuilder typeBuilder = _ModuleBuilder.DefineType(newTypeName,
TypeAttributes.Public
| TypeAttributes.Class
| TypeAttributes.AutoClass
| TypeAttributes.AnsiClass
| TypeAttributes.ExplicitLayout);
int fieldOffset = 0;
FieldBuilder fieldBuilder = typeBuilder.DefineField("Status", typeof(Data.Response.ResponseStatus), FieldAttributes.Public);
fieldBuilder.SetOffset(fieldOffset);
fieldOffset += IntPtr.Size;
fieldBuilder = typeBuilder.DefineField("ErrorMessage", typeof(string), FieldAttributes.Public);
fieldBuilder.SetOffset(fieldOffset);
fieldOffset += IntPtr.Size;
fieldBuilder = typeBuilder.DefineField("ErrorDetails", typeof(string), FieldAttributes.Public);
fieldBuilder.SetOffset(fieldOffset);
fieldOffset += IntPtr.Size;
fieldBuilder = typeBuilder.DefineField("Logon", typeof(Data.Logon), FieldAttributes.Public);
fieldBuilder.SetOffset(fieldOffset);
fieldOffset += IntPtr.Size;
fieldBuilder = typeBuilder.DefineField("Object", typeof(List<T>), FieldAttributes.Public);
fieldBuilder.SetOffset(fieldOffset);
fieldOffset += IntPtr.Size;
// Add newly create type to static list
Type type = typeBuilder.CreateType();
_ResponseTypes.Add(newTypeName, type);
return type;
}
}
}
After getting the HttpResponseMessage from the Web API call, we deserialize the json response as follows:
Response response = new Response();
Type dynamicResponseType = ResponseBuilder.CreateResponseType<T>();
JsonSerializer serializer = new JsonSerializer();
System.IO.Stream stream = httpresponse.Content.ReadAsStreamAsync().Result;
using (System.IO.StreamReader sr = new System.IO.StreamReader(stream))
{
using (JsonTextReader jtr = new JsonTextReader(sr))
{
var x = serializer.Deserialize(jtr, dynamicResponseType);
FieldInfo fi = dynamicResponseType.GetField("Status");
response.Status = (Response.ResponseStatus)fi.GetValue(x);
fi = dynamicResponseType.GetField("ErrorMessage");
response.ErrorMessage = (string)fi.GetValue(x);
fi = dynamicResponseType.GetField("ErrorDetails");
response.ErrorDetails = (string)fi.GetValue(x);
fi = dynamicResponseType.GetField("Logon");
response.Logon = (Logon)fi.GetValue(x);
fi = dynamicResponseType.GetField("Object");
response.Object = fi.GetValue(x);
}
}
This works perfectly and is quick at creating the List<T> response.Object.
Now, on to my issue. I am migrating away from Newtonsoft.Json to System.Text.Json in .NET Framework 4.8. I am using System.Text.Json version 9.0.6 nuGet package.
This is the last part of Newtonsoft code I need to migrate and I am having issue with deserializing directly into my dynamicResponseType.
JsonTextReader is not available in System.Text.Json, I have tried playing around with Uft8JsonReader but this doesn't seem to help.
I have tried:
var x = JsonSerializer.Deserialize(httpresponse.Content.ReadAsStreamAsync().Result, dynamicResponseType);
x is instantiated, but all the properties are null.
Newtonsoft.Json"idiomatically", but it's hard to tell (but it would be great to see the benchmarks and minimal reproducible example).