Search code examples
asp.net-core.net-coreserializationjson.net

How to declare .NET 8 TypeConverter to control the serialization of a class


I want to change the return Types of an existing ASP.NET Core 8 Web API project. The big problem is that I can't change the response JSON because this services are already in production.

For example, this is an existing web method:

public async Task<JsonResult> InsertItem(InsertItemInput oInputParams)
{
    Stopwatch oStopWatch = Stopwatch.StartNew();
    int iResult = 0;
    iResult = // some code to insert into the db

    return new JsonResult(new 
                          {
                              StatusCode = (int)HttpStatusCode.OK,
                              Exception = (object)null,
                              Result = iResult,
                              ServiceFullElapsed = oStopWatch.Elapsed.TotalMilliseconds
                          });
}

This is the result:

{
    "StatusCode": 200,
    "Exception": null,
    "Result": 157,
    "ServiceFullElapsed": 140.2169
}

I want to create models for the result, so I created this class:

[TypeConverter(typeof(ReadWriteResultJsonConvert))]
public class ReadWriteResult
{
    public int Code {  get; set; }
}

ReadWriteResultJsonConvert:

public class ReadWriteResultJsonConvert : Newtonsoft.Json.JsonConverter<ReadWriteResult>
{
    public override void WriteJson(JsonWriter writer, ReadWriteResult value, Newtonsoft.Json.JsonSerializer serializer)
    {
        var obj = (JObject)JToken.FromObject(value.Code);
        obj.WriteTo(writer);
    }

    public override ReadWriteResult ReadJson(JsonReader reader, Type objectType, ReadWriteResult existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
    {
        return existingValue;
    }
}

But even changing the web method like this:

public async Task<JsonResult> InsertItem(InsertItemInput oInputParams)
{
    Stopwatch oStopWatch = Stopwatch.StartNew();
    ReadWriteResult oResult = new ReadWriteResult();
    oResult.Code = // some code to insert into the db

    return new JsonResult(new
                          {
                              StatusCode = (int)HttpStatusCode.OK,
                              Exception = (object)null,
                              Result = oResult,
                              ServiceFullElapsed = oStopWatch.Elapsed.TotalMilliseconds
                          });
}

The converter is never called and the response changes:

{
    "StatusCode": 200,
    "Exception": null,
    "Result": { "Code": 157 },
    "ServiceFullElapsed": 28064.9097
}

I do not want "Result": { "Code": 157 } but "Result":157

Is there a way to serialize the class ReadWriteResult using only the Code property?


Solution

  • public class ReadWriteResultJsonConvert : Newtonsoft.Json.JsonConverter<ReadWriteResult>
     {
    

    and

    return new JsonResult(new
                          {
                              StatusCode = (int)HttpStatusCode.OK,
                              Exception = (object)null,
                              Result = oResult,
                              ServiceFullElapsed = oStopWatch.Elapsed.TotalMilliseconds
                          });
    

    In your code, the custom JsonConverter is created for the ReadWriteResult model, but in the controller, you will return an Anonymous Object, so it will not trigger the ReadWriteResultJsonConvert JsonConverter.

    To convert the anonymous object, you can create and use the following AnonymousObjectConverter:

    public class AnonymousObjectConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            // You can customize this to only accept certain types if needed
            return true;
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            // Custom serialization logic
            var jsonObject = value as dynamic; // You can use dynamic to access anonymous properties
            writer.WriteStartObject();
    
            foreach (var property in jsonObject.GetType().GetProperties())
            {
                writer.WritePropertyName(property.Name);
                //writer.WriteValue(property.GetValue(jsonObject, null));
    
                var propertyValue = property.GetValue(jsonObject, null);
    
                // Check if propertyValue is null
                if (propertyValue == null)
                {
                    writer.WriteNull(); // Write null for null values
                }
                else
                {
                    //check if the property is ReadWriteResult
                    if (propertyValue.GetType() == typeof(ReadWriteResult))
                    {
                        writer.WriteValue(propertyValue.Code);
                    }
                    else
                    { 
                        writer.WriteValue(propertyValue);
                    }
                }
            }
    
            writer.WriteEndObject();
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        { 
            return existingValue;//obj; // Returning an ExpandoObject; you can modify this as needed
        }
    }
    

    Then, register the custom converter in the Program.cs file:

    builder.Services.AddControllers().AddNewtonsoftJson(options =>
    { 
       // options.SerializerSettings.Converters.Add(new ReadWriteResultJsonConvert());
        options.SerializerSettings.Converters.Add(new AnonymousObjectConverter());
    });
    

    And, in the controller, we could use the following code:

        [HttpPost("interitem")]
        public async Task<JsonResult> InsertItem()
        {
            Stopwatch oStopWatch = Stopwatch.StartNew();
            int iResult = 0;
            iResult = 567;  // some code to insert into the db
    
            ReadWriteResult oResult = new ReadWriteResult();
            oResult.Code = 567;  // some code to insert into the db
    
            return new JsonResult(new
            {
                StatusCode = (int)HttpStatusCode.OK,
                Exception = (object)null,
                Result = oResult,
                ServiceFullElapsed = oStopWatch.Elapsed.TotalMilliseconds
            });
        }
    

    After running the application, the result as below:

    Success