-1

JsonConvert.SerializeObject is not working properly. restData has the property SuccessfulActivities and it has some values. But When I serialize it is serializing to an empty value for SuccessfulActivities. like the below. Its.Net 6 project. Please help.

{ "SuccessfulActivities": [ {} ], "Status": "{"Type":"INFO","Message":"OK"}", "RequestId": "bcb8b790-2cd8-4b2b-b05a-414a3c7ceb0b", "TenantId": "11111111-1111-1111-1111-84ba20252626" }

public string SerializeRestResponse(IRestResponse restData)
{
    JsonSerializerSettings jsonSettings = new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, ContractResolver = new RestQueryResponseContractResolver() };
    return JsonConvert.SerializeObject(restData, jsonSettings);
}


public class RestQueryResponseContractResolver : DefaultContractResolver
{
    List<string> _properties;

    public RestQueryResponseContractResolver()
    {
        _properties = new List<string>();
        //_properties.Add(p.Name); This is just to show that I am adding other properties as well from different object type.
        PropertyInfo[] props2 = typeof(IRestBotResponse).GetProperties();
        foreach (PropertyInfo p in props2)
            _properties.Add(p.Name);
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);

        property.ShouldSerialize = instance => _properties.Contains(property.PropertyName);

        return property;
    }
}

public interface IRestBotResponse : IRestResponse
{
    /// <summary>
    /// The list of successful conversation activities that were sent.
    /// For the Tuple:
    ///   Item1 is the user identifier (the user's Azure AD ID)
    ///   Item2 is the ActivityID
    /// </summary>
    [Required]
    List<(string, string)> SuccessfulActivities { get; }
}

I am able to get other properties properly. But it has a problem only with SuccessfulActivities and I think the reason is that SuccessfulActivities is a list of tuple List<(string, string)>.

I have read this answer, but it suggests using System.Text.Json, But I want to use NewtonSoft.Json as it's already being used in the project at many places. As its already in production working project so I want to make changes with minimal side effects.

Edit:

I am using this JSON response (responseJson) to send it to the client as the response of the HTTP request like this.

return new ContentResult()
{
    Content = responseJson,
    ContentType = "application/json; charset=utf-8",
    StatusCode = (int)statusCode,
};

Edit 2:

As suggested by @Panagiotis Kanavos, I tried using the custom JsonConverter but it is also not considering the SuccessfulActivities. It's not even hitting the breakpoint on the methods WriteJson and ReadJson. Please find below the relevant implementation.

public class RestBotResponse : RestResponse, IRestBotResponse
{
    public RestBotResponse()
    {
    }

    [JsonConstructor]
    public RestBotResponse(List<(string, string)> successfulActivities, string status, string tenantId, string requestId)
        : base(status, requestId, tenantId)
    {
        SuccessfulActivities = successfulActivities;
    }

    [Required]
    [JsonConverter(typeof(TupleListConverter<string, string>))]
    public List<(string, string)> SuccessfulActivities { get; set; }
}

public abstract class RestResponse : IRestResponse
{
    protected RestResponse()
    {
    }

    protected RestResponse(string status, string requestId, string tenantId)
    {
        Status = status;
        RequestId = requestId;
        if (tenantId == null)
            tenantId = string.Empty;
        TenantId = tenantId;
    }

    [Required]
    public string Status { get; set; }

    public string RequestId { get; set; }

    public string TenantId { get; set; }
}

public class TupleListConverter<T1, T2> : JsonConverter<List<(T1, T2)>>
{
    public override void WriteJson(JsonWriter writer, List<(T1, T2)> value, JsonSerializer serializer)
    {
        var array = value.Select(t => new { Item1 = t.Item1, Item2 = t.Item2 }).ToArray();
        serializer.Serialize(writer, array);
    }

    public override List<(T1, T2)> ReadJson(JsonReader reader, Type objectType, List<(T1, T2)> existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override bool CanRead => false;
}

Edit 3:

Please find the implementation of RestQueryResponseContractResolver

public class RestQueryResponseContractResolver : DefaultContractResolver
{
    List<string> _properties;

    public RestQueryResponseContractResolver()
    {
        _properties = new List<string>();

        // Include all ITeamUserInfo properties
        PropertyInfo[] teamUserInfoProps = typeof(ITeamUserInfo).GetProperties();
        foreach (PropertyInfo p in teamUserInfoProps)
            _properties.Add(p.Name);

        // Include all RestQueryResponse properties
        PropertyInfo[] restResponseProps = typeof(RestQueryResponse).GetProperties();
        foreach (PropertyInfo p in restResponseProps)
            _properties.Add(p.Name);

        //PropertyInfo[] props2 = typeof(IRestBotResponse).GetProperties();
        //foreach (PropertyInfo p in props2)
        //    _properties.Add(p.Name);
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);

        property.ShouldSerialize = instance => _properties.Contains(property.PropertyName);

        return property;
    }
}
Vivek Nuna
  • 25,472
  • 25
  • 109
  • 197
  • and why you are using `List<(string, string)>` instead simple `Dictionary` ... is `Item1` value repeating ? – Selvin Jul 18 '23 at 09:16
  • As far as I remember, Tuples could be problematic. I just forgot why exactly. – Fildor Jul 18 '23 at 09:17
  • @Selvin Its old legacy code, there might be some reasons to use as `List<(string, string)>` – Vivek Nuna Jul 18 '23 at 09:17
  • 1
    Tuples have fields, not properties. Serializers work with properties. The fields are named `Item1`, `Item2` etc. which makes for *terrible* names. – Panagiotis Kanavos Jul 18 '23 at 09:18
  • @PanagiotisKanavos Yes, you are right, so how to solve this now – Vivek Nuna Jul 18 '23 at 09:19
  • `old legacy code` you'll have to fix it. You can map that property to a proper DTO either with a `Select` or with AutoMapper. You could use a `record` instead of a tuple – Panagiotis Kanavos Jul 18 '23 at 09:19
  • Or you can create a [custom converter](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/converters-how-to?pivots=dotnet-6-0) just for this property, that emits the data into whatever form you want. *What* form do you want though? Do you want to emit `{'Item1':'blah', 'Item2':'blah'}`? Or something more meaningful? What do the *consumers* of this JSON expect? – Panagiotis Kanavos Jul 18 '23 at 09:20
  • @PanagiotisKanavos I have added the Edit section to my question, Please check – Vivek Nuna Jul 18 '23 at 09:24
  • That's not related to the question but it *is* unnecessary. Simply returning a DTO with `return Ok(thatObject)` will return it as JSON with the correct content type. You can create custom converters in JSON.NET too. What do you want to emit though? `Item1`? `UserID` ? – Panagiotis Kanavos Jul 18 '23 at 09:30
  • @PanagiotisKanavos this should be fine `{'Item1':'blah', 'Item2':'blah'}` – Vivek Nuna Jul 18 '23 at 09:31
  • You can create [custom converters in JSON.NET](https://www.newtonsoft.com/json/help/html/CustomJsonConverterGeneric.htm) too and apply them to specific properties or classes [JsonConverterAttribute](https://www.newtonsoft.com/json/help/html/JsonConverterAttributeProperty.htm) – Panagiotis Kanavos Jul 18 '23 at 09:35
  • @PanagiotisKanavos I have tried the custom converter, but it's not working. I have added the details in the question, Please check – Vivek Nuna Jul 18 '23 at 10:24
  • @PanagiotisKanavos In my case, I am using the `ContractResolver = new RestQueryResponseContractResolver()` as well and due to this, It's not calling the custom converter. When I commented `ContractResolver = new RestQueryResponseContractResolver() `. I am getting the value of `SuccessfulActivities`. But I want to use `ContractResolver ` as well – Vivek Nuna Jul 18 '23 at 11:14
  • Why do this instead of configuring ASP.NET Core to use JSON.NET once? The project is clearly doing something unconventional and without the actual code it's hard to guess. What's the point of `RestQueryResponseContractResolver`? Where does `responseJson` come from? – Panagiotis Kanavos Jul 18 '23 at 11:15
  • @PanagiotisKanavos Its using `RestQueryResponseContractResolver` as the `restData` has many fields and We want to only return the selected fields and return to the client. I have also added the implementation of `RestQueryResponseContractResolver` to the question. Please check – Vivek Nuna Jul 18 '23 at 12:12

1 Answers1

-1

You have to create a converter for tuples:

public class TupleConverter : JsonConverter<(string, string)>
{
    public override (string, string) ReadJson(JsonReader reader, Type objectType, (string, string) existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, (string, string) value, JsonSerializer serializer)
    {
        writer.WriteStartObject();
        writer.WritePropertyName(value.Item1);
        writer.WriteValue(value.Item2);
        writer.WriteEndObject();
    }
}

Then, when you serialize a corresponding instance, you have to provide the converter within the settings:

var settings = new JsonSerializerSettings{Converters = {new TupleConverter()}};
var json = JsonConvert.SerializeObject(source, settings);

The full example would be this:

public class Program
{
    public static void Main()
    {
        var source = new Source();
        source.Items.Add(("a", "b"));
        source.Items.Add(("c", "d"));
        var settings = new JsonSerializerSettings{Converters = {new TupleConverter()}};
        var json = JsonConvert.SerializeObject(source, settings);
        Console.WriteLine(json);
    }
}

public class Source
{
    public List<(string, string)> Items { get; } = new List<(string, string)>();
}

public class TupleConverter : JsonConverter<(string, string)>
{
    public override (string, string) ReadJson(JsonReader reader, Type objectType, (string, string) existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, (string, string) value, JsonSerializer serializer)
    {
        writer.WriteStartObject();
        writer.WritePropertyName(value.Item1);
        writer.WriteValue(value.Item2);
        writer.WriteEndObject();
    }
}

And the result of this code would be:

{"Items":[{"a":"b"},{"c":"d"}]}

Oliver
  • 43,366
  • 8
  • 94
  • 151
  • As I mentioned in my question, I have other fields as well which also I want to be considered which are not of type `List<(string, string)>`. have you also checked my updated question? – Vivek Nuna Jul 18 '23 at 10:48
  • In my case, I am using the `ContractResolver = new RestQueryResponseContractResolver() ` as well and due to this, It's not calling the custom converter. When I commented `ContractResolver = new RestQueryResponseContractResolver() `. I am getting the value of `SuccessfulActivities ` – Vivek Nuna Jul 18 '23 at 11:07