0

I am running an ASP.NET Core 5 MVC project and tried to convert JSON response object from a Web API call to a list of ProductPromotionModels:

dynamic likesResult = await response.Content.ReadAsStringAsync(); 
var objs = Newtonsoft.Json.JsonConvert.DeserializeObject<List<ProductPromotionModel>>(likesResult.ToString());  

When I run this code in my application, I get this deserializing error:

Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.TimeSpan' because the type requires a JSON primitive value (e.g. string, number, boolean, null) to deserialize correctly. To fix this error either change the JSON to a JSON primitive value (e.g. string, number, boolean, null) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
Path '$values[0].startTime.ticks', line 1, position 287

According to error message, the issue is in the startTime (Timespan property) property. So I have used JsonConverter, below is the json object:

{
  "$id": "1",
  "$values": [
    {
      "startTime": {
        "ticks": 36000000000,
        "days": 0,
        "hours": 1,
        "milliseconds": 0,
        "minutes": 0,
        "seconds": 0,
        "totalDays": 0.041666666666666664,
        "totalHours": 1,
        "totalMilliseconds": 3600000,
        "totalMinutes": 60,
        "totalSeconds": 3600
      },
      "endTime": {
        "ticks": 36000000000,
        "days": 0,
        "hours": 1,
        "milliseconds": 0,
        "minutes": 0,
        "seconds": 0,
        "totalDays": 0.041666666666666664,
        "totalHours": 1,
        "totalMilliseconds": 3600000,
        "totalMinutes": 60,
        "totalSeconds": 3600
      }
    }
  ]
}

Entity class

public partial class ProductPromotionModel 
{
    // ...

    [Display(Name = "Start Time")]
    [DataType(DataType.Time)]  
    [JsonConverter(typeof(TimeSpanToStringConverter), null)]
    public TimeSpan StartTime { get; set; }

    [Display(Name = "End Time")]
    [DataType(DataType.Time)]
    [JsonConverter(typeof(TimeSpanToStringConverter),null)]
    public TimeSpan EndTime { get; set; }
        
    // ...
}

Converter class

public class TimeSpanToStringConverter : JsonConverter<TimeSpan>
{
    public override TimeSpan ReadJson(JsonReader reader, Type objectType, TimeSpan existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
    {
        int days = 0;
        int hours = 0;
        int minutes = 0;
        int seconds = 0;
        int milliseconds = 0;

        TimeSpan model = new TimeSpan();

        if (reader.TokenType != JsonToken.StartObject)
        {
            throw new Newtonsoft.Json.JsonException();
        }

        while (reader.Read())
        {
            if (reader.TokenType == JsonToken.EndObject)
            {
                break;
            }

            if (reader.TokenType == JsonToken.PropertyName)
            {
                string propertyName = reader.ReadAsString();                                

                switch (propertyName)
                {
                    case "days":
                        days = (int)reader.ReadAsInt32();
                        break;

                    case "hours":
                        hours = (int)reader.ReadAsInt32();
                        break;

                    case "milliseconds":
                        milliseconds = (int)reader.ReadAsInt32();
                        break;

                    case "minutes":
                        minutes = (int)reader.ReadAsInt32();
                        break;

                    case "seconds":
                        seconds = (int)reader.ReadAsInt32();
                        break;

                    default:
                        break;
                }
            }
        }

        if (reader.TokenType == JsonToken.EndObject)
        {
            model = new TimeSpan(days, hours, minutes, seconds, milliseconds);
        }

        return model;
    }

    public override void WriteJson(JsonWriter writer, TimeSpan value, Newtonsoft.Json.JsonSerializer serializer)
    {
        return;
    }
}

When I debug the converter, Propertyname show as numbers not the PropertyName as below. How can I get the PropertyName using JsonReader?

enter image description here

dbc
  • 104,963
  • 20
  • 228
  • 340
NJkp
  • 37
  • 8
  • 1
    Your bug is that `JsonReader.ReadAsString()` reads the **next** token as a string, see [Custom JsonConverter not working when using JsonReader instead of JsonSerializer](https://stackoverflow.com/a/46025896/3744182). You need to use `(string) reader.Value` to get the **current** string. But just loading the entire thing into a `JObject` (or DTO) as suggested in @Serge's answer is going to be much simpler and avoid fringe bugs such as failing to properly skip unexpected property values when the value is not a primitive, or failing to throw an exception on a truncated file. – dbc Mar 10 '23 at 19:22
  • I don't know who created your API, but there is an ISO format for periods (TimeSpan). Splitting each timespan into 11 segments is not efficient for reading or writing. You almost have the same thing repeated several times. – Neil Mar 11 '23 at 16:07

2 Answers2

2

you have a wrong syntax, read as a string always returns string, also converter could be much more simple

string json = await response.Content.ReadAsStringAsync(); 

List<ProductPromotionModel> models = JObject.Parse(json)["$values"]
                                     .ToObject<List<ProductPromotionModel>>();


public class TimeSpanToStringConverter : JsonConverter<TimeSpan>
{
    public override TimeSpan ReadJson(JsonReader reader, Type objectType, TimeSpan existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
    {
        var o = JObject.Load(reader);
        return new TimeSpan( (int) o["days"], (int) o["hours"], (int) o["minutes"], (int) o["seconds"],(int) o["milliseconds"]);
    }

    public override void WriteJson(JsonWriter writer, TimeSpan value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override bool CanWrite => false;
}
Serge
  • 40,935
  • 4
  • 18
  • 45
0

When I replace the code string propertyName = reader.ReadAsString() by string propertyName = reader.Value.ToString() issue was fixed in the converter class.

Also startTime and EndTime getting from Sql Time Columns as below image:

enter image description here

dbc
  • 104,963
  • 20
  • 228
  • 340
NJkp
  • 37
  • 8
  • 1
    *What is the wrong in my post?* -- honestly not sure, your question looks OK to me. Maybe people saw the code image and thought that you weren't including your code as text -- which you actually were including along with the screen shot. Also, did you use [this answer](https://stackoverflow.com/a/46025896/3744182) from [Custom JsonConverter not working when using JsonReader instead of JsonSerializer](https://stackoverflow.com/q/46024882/3744182)? If so you should probably give credit in your answer. – dbc Mar 12 '23 at 23:24