Deserialise an API response.

buda56

New member
Joined
Jul 13, 2015
Messages
1
Location
Adelaide
Programming Experience
5-10
0
Been looking at this for a few hours now and am struggling to work out how to deserialize the response from a call to a Web API. We had a previous version that worked fine but the Endpoint structure changed after an update to API and as a result the code no longer works.
Basically I am trying to extract the Tasks returned from the results of the API call, see below for a structure of the returned API call from Postman.
JavaScript:
"Version": 1,
"Result": "Success",
"Data": {
    "Order": {
        "Oid": "f3a3afa9-ac2b-4914-b3cb-585c761f2dfb",
        "Did": 265532,
        "Tasks": [
            {
                "Oid": "af82ce50-0587-4752-ba3b-d60dbfebe041",
                "CreatedOn": "2023-05-19T02:24:55.437Z",
                "ChangedOn": "2023-05-19T02:45:26.28Z",
                "Description": "",
                "Comment": "",
                "IsStarted": false,
                "ScheduledOn": "2023-05-19T12:14:51.507",
                "ProductionDeadline": "2023-05-21T16:00:00",
                "ScheduledStart": "2023-05-19T11:56:00",
                "ScheduledEnd": "2023-05-19T12:04:00",
                "RealStart": "0001-01-01T00:00:00",
                "RealEnd": "0001-01-01T00:00:00",
                "OrderItemPartsCount": 27,
                "CalculatedSeconds": "120",
                "IdleSeconds": "0",
                "ScheduledSeconds": 480,
                "RealSeconds": 0,
                "PercentComplete": 0,
                "HasFinishedItems": false,
                "IsCustomTask": false,
                "AppointmentType": "OrderSpecificAppointment",
                "Station": {
                    "_Value": "Centurio",
                    "oid": "f7f3c5fe-49f0-4a9a-b366-29c4ab990fc7"
                },
                "MasterTask": {
                    "_Value": "",
                    "oid": "179212f4-ff0e-465d-962c-51b7da711bb7"
                }
            },
            {
                "Oid": "72b6a0ba-cbc2-4f9b-9372-86805d40f7e3",
                "CreatedOn": "2023-05-19T02:24:55.437Z",
                "ChangedOn": "2023-05-19T02:25:28.37Z",
                "Description": "",
                "Comment": "",
                "IsStarted": false,
                "ScheduledOn": "2023-05-19T11:54:55.437",
                "ProductionDeadline": "2023-05-21T16:00:00",
                "ScheduledStart": "2023-05-19T12:11:00",
                "ScheduledEnd": "2023-05-19T13:10:00",
                "RealStart": "0001-01-01T00:00:00",
                "RealEnd": "0001-01-01T00:00:00",
                "OrderItemPartsCount": 27,
                "CalculatedSeconds": "3540",
                "IdleSeconds": "0",
                "ScheduledSeconds": 3540,
                "RealSeconds": 0,
                "PercentComplete": 0,
                "HasFinishedItems": false,
                "IsCustomTask": false,
                "AppointmentType": "OrderSpecificAppointment",
                "Station": {
                    "_Value": "Twinmatic",
                    "oid": "d92a32c3-0763-49d7-a8eb-77ea68afa311"
                }
            }
        ]
    }
},
"Details": ""

}
In the previous version there was no "Order" element, can anyone assist in showing how I can get access to the Tasks as a List so I can process them?
Regards Peter.
 
Last edited by a moderator:
If you are using JSON.NET just use the LINQ to JSON to find the Tasks, and then deserialize that fragment.

But you never mentioned how exactly you used to deserialize the JSON you got from the API, nor did you say what error you are getting with your old code reading this new data format, nor did you show us your old code.
 
If it's fairly simplistic, like you have there, you take your JSON and you paste it into htpp://app.quicktype.io and choose some names, code language and other things

It generates these classes, that you paste into a new empty file in your project:

C#:
// <auto-generated />
//
// To parse this JSON data, add NuGet 'System.Text.Json' then do:
//
//    using QuickType;
//
//    var welcome = Welcome.FromJson(jsonString);
#nullable enable
#pragma warning disable CS8618
#pragma warning disable CS8601
#pragma warning disable CS8603

namespace QuickType
{
    using System;
    using System.Collections.Generic;

    using System.Text.Json;
    using System.Text.Json.Serialization;
    using System.Globalization;

    public partial class Welcome
    {
        [JsonPropertyName("Version")]
        public long Version { get; set; }

        [JsonPropertyName("Result")]
        public string Result { get; set; }

        [JsonPropertyName("Data")]
        public Data Data { get; set; }

        [JsonPropertyName("Details")]
        public string Details { get; set; }
    }

    public partial class Data
    {
        [JsonPropertyName("Order")]
        public Order Order { get; set; }
    }

    public partial class Order
    {
        [JsonPropertyName("Oid")]
        public Guid Oid { get; set; }

        [JsonPropertyName("Did")]
        public long Did { get; set; }

        [JsonPropertyName("Tasks")]
        public Task[] Tasks { get; set; }
    }

    public partial class Task
    {
        [JsonPropertyName("Oid")]
        public Guid Oid { get; set; }

        [JsonPropertyName("CreatedOn")]
        public DateTimeOffset CreatedOn { get; set; }

        [JsonPropertyName("ChangedOn")]
        public DateTimeOffset ChangedOn { get; set; }

        [JsonPropertyName("Description")]
        public string Description { get; set; }

        [JsonPropertyName("Comment")]
        public string Comment { get; set; }

        [JsonPropertyName("IsStarted")]
        public bool IsStarted { get; set; }

        [JsonPropertyName("ScheduledOn")]
        public DateTimeOffset ScheduledOn { get; set; }

        [JsonPropertyName("ProductionDeadline")]
        public DateTimeOffset ProductionDeadline { get; set; }

        [JsonPropertyName("ScheduledStart")]
        public DateTimeOffset ScheduledStart { get; set; }

        [JsonPropertyName("ScheduledEnd")]
        public DateTimeOffset ScheduledEnd { get; set; }

        [JsonPropertyName("RealStart")]
        public DateTimeOffset RealStart { get; set; }

        [JsonPropertyName("RealEnd")]
        public DateTimeOffset RealEnd { get; set; }

        [JsonPropertyName("OrderItemPartsCount")]
        public long OrderItemPartsCount { get; set; }

        [JsonPropertyName("CalculatedSeconds")]
        [JsonConverter(typeof(ParseStringConverter))]
        public long CalculatedSeconds { get; set; }

        [JsonPropertyName("IdleSeconds")]
        [JsonConverter(typeof(ParseStringConverter))]
        public long IdleSeconds { get; set; }

        [JsonPropertyName("ScheduledSeconds")]
        public long ScheduledSeconds { get; set; }

        [JsonPropertyName("RealSeconds")]
        public long RealSeconds { get; set; }

        [JsonPropertyName("PercentComplete")]
        public long PercentComplete { get; set; }

        [JsonPropertyName("HasFinishedItems")]
        public bool HasFinishedItems { get; set; }

        [JsonPropertyName("IsCustomTask")]
        public bool IsCustomTask { get; set; }

        [JsonPropertyName("AppointmentType")]
        public string AppointmentType { get; set; }

        [JsonPropertyName("Station")]
        public MasterTask Station { get; set; }

        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        [JsonPropertyName("MasterTask")]
        public MasterTask MasterTask { get; set; }
    }

    public partial class MasterTask
    {
        [JsonPropertyName("_Value")]
        public string Value { get; set; }

        [JsonPropertyName("oid")]
        public Guid Oid { get; set; }
    }

    public partial class Welcome
    {
        public static Welcome FromJson(string json) => JsonSerializer.Deserialize<Welcome>(json, QuickType.Converter.Settings);
    }

    public static class Serialize
    {
        public static string ToJson(this Welcome self) => JsonSerializer.Serialize(self, QuickType.Converter.Settings);
    }

    internal static class Converter
    {
        public static readonly JsonSerializerOptions Settings = new(JsonSerializerDefaults.General)
        {
            Converters =
            {
                new DateOnlyConverter(),
                new TimeOnlyConverter(),
                IsoDateTimeOffsetConverter.Singleton
            },
        };
    }

    internal class ParseStringConverter : JsonConverter<long>
    {
        public override bool CanConvert(Type t) => t == typeof(long);

        public override long Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            var value = reader.GetString();
            long l;
            if (Int64.TryParse(value, out l))
            {
                return l;
            }
            throw new Exception("Cannot unmarshal type long");
        }

        public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)
        {
            JsonSerializer.Serialize(writer, value.ToString(), options);
            return;
        }

        public static readonly ParseStringConverter Singleton = new ParseStringConverter();
    }
 
    public class DateOnlyConverter : JsonConverter<DateOnly>
    {
        private readonly string serializationFormat;
        public DateOnlyConverter() : this(null) { }

        public DateOnlyConverter(string? serializationFormat)
        {
            this.serializationFormat = serializationFormat ?? "yyyy-MM-dd";
        }

        public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            var value = reader.GetString();
            return DateOnly.Parse(value!);
        }

        public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options)
            => writer.WriteStringValue(value.ToString(serializationFormat));
    }

    public class TimeOnlyConverter : JsonConverter<TimeOnly>
    {
        private readonly string serializationFormat;

        public TimeOnlyConverter() : this(null) { }

        public TimeOnlyConverter(string? serializationFormat)
        {
            this.serializationFormat = serializationFormat ?? "HH:mm:ss.fff";
        }

        public override TimeOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            var value = reader.GetString();
            return TimeOnly.Parse(value!);
        }

        public override void Write(Utf8JsonWriter writer, TimeOnly value, JsonSerializerOptions options)
            => writer.WriteStringValue(value.ToString(serializationFormat));
    }

    internal class IsoDateTimeOffsetConverter : JsonConverter<DateTimeOffset>
    {
        public override bool CanConvert(Type t) => t == typeof(DateTimeOffset);

        private const string DefaultDateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK";

        private DateTimeStyles _dateTimeStyles = DateTimeStyles.RoundtripKind;
        private string? _dateTimeFormat;
        private CultureInfo? _culture;

        public DateTimeStyles DateTimeStyles
        {
            get => _dateTimeStyles;
            set => _dateTimeStyles = value;
        }

        public string? DateTimeFormat
        {
            get => _dateTimeFormat ?? string.Empty;
            set => _dateTimeFormat = (string.IsNullOrEmpty(value)) ? null : value;
        }

        public CultureInfo Culture
        {
            get => _culture ?? CultureInfo.CurrentCulture;
            set => _culture = value;
        }

        public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
        {
            string text;


            if ((_dateTimeStyles & DateTimeStyles.AdjustToUniversal) == DateTimeStyles.AdjustToUniversal
                || (_dateTimeStyles & DateTimeStyles.AssumeUniversal) == DateTimeStyles.AssumeUniversal)
            {
                value = value.ToUniversalTime();
            }

            text = value.ToString(_dateTimeFormat ?? DefaultDateTimeFormat, Culture);

            writer.WriteStringValue(text);
        }

        public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            string? dateText = reader.GetString();

            if (string.IsNullOrEmpty(dateText) == false)
            {
                if (!string.IsNullOrEmpty(_dateTimeFormat))
                {
                    return DateTimeOffset.ParseExact(dateText, _dateTimeFormat, Culture, _dateTimeStyles);
                }
                else
                {
                    return DateTimeOffset.Parse(dateText, Culture, _dateTimeStyles);
                }
            }
            else
            {
                return default(DateTimeOffset);
            }
        }


        public static readonly IsoDateTimeOffsetConverter Singleton = new IsoDateTimeOffsetConverter();
    }
}
#pragma warning restore CS8618
#pragma warning restore CS8601
#pragma warning restore CS8603

Then you install a nuget package called Flurl.Http and its dependent Flurl

Then you write one line of code inside an async method, like:

C#:
  var r = await "http://url.to.your.api.here".GetJsonAsync<Welcome>();   //change Welcome for whatever you called your root object in quicktype. Use postJson... instead of Get if your API requires a POST

That's it. You can get the tasks returned like:

C#:
  var t = r.Data.Order.Tasks;

If your JSON is more complex and QuickType has written involved serializers for it, you can GetString instead and then call for the helpers QT wrote to deserialize like:

C#:
  var json = await "url here".GetStringAsync();
  var r = Welcome.FromJson(json);

This way gets the API result as a stirng and then asks QT's customized serializer to deser it. It is useful for things like when you have a property that is sometimes an object and sometimes an array.

Using Flurl's GetJsonAsync<T> will just straight use Newtonsoft (i think) to deser the JSON to a T without any custom serializers, which may not work out if the josn is complex (QT is quite good at detecting complex serialization scenarios and writing serializers for them)
 
Last edited:
Back
Top Bottom