Home > other >  Deserialize complex json into an object
Deserialize complex json into an object

Time:10-20

I have a strange json, which:

  • I can not change it, it is from a third party
  • It can have up to 75 properties

A simple example of the json:

{
   "bookingcode":["ABC","DEF", "GHJ", "TEST"],
   "referencenumber":[123, 456]
   "bookingdate":["22-07-2022T14:00:30", "23-11-2022T17:00:25"]
}

I am only interested in the last value of the array and want to deserialize into a class called Booking, so in this case bookingcode="TEST" and bookingdate="23-11-2022T17:00:25".

A solution would be define a model:

public class Booking
{
    public string BookingCode { get; set; }
    public int ReferenceNumber { get; set; }
    public DateTime BookingDate { get; set;}
}

public class BookingInput
{
     public List<string> BookingCode { get; set;}
     public List<int> ReferenceNumber { get; set; }
     public List<DateTime> BookingDate { get; set; }
}

var bookinginput = JsonSerializer.Deserialize<BookingInput>(jsonString);
var booking = new Booking
{
     BookingCode = bookinginput.BookingCode.Last(),
     ReferenceNumber = bookinginput.ReferenceNumber.Last(),
     ...
}

I think it is very tedious to write out all 75 properties in code like this. Is there a better solution in Json.net or System.Text.Json for this?

CodePudding user response:

Using NewtonSoft.Json, you can create your own JsonConverter. Below I have created a LastArrayItemJsonConverter, which takes the last item from an array of a certain type and returns that.

public class LastArrayItemJsonConverter<TItem> : JsonConverter<TItem>
{
    private string _formatString;
    
    public LastArrayItemJsonConverter()
    { }
    
    public LastArrayItemJsonConverter(string formatString)
    {
        _formatString = formatString;
    }
    
    public override TItem ReadJson(JsonReader reader, Type objectType, TItem existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        if (typeof(TItem) == typeof(DateTime) || typeof(TItem) == typeof(DateTime?))
            reader.DateFormatString = _formatString;
        
        TItem result = default;
        if (reader.TokenType != JsonToken.StartArray)
            return default;
        
        while (reader.Read() && reader.TokenType != JsonToken.EndArray)
            result = (TItem)Convert.ChangeType(reader.Value, typeof(TItem));
        
        return result;
    }

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

By decorating your model, you can specify that the serializer should use the converter to convert the properties:

public class Booking
{
    [JsonConverter(typeof(LastArrayItemJsonConverter<string>))]
    public string BookingCode { get; set; }
    [JsonConverter(typeof(LastArrayItemJsonConverter<int>))]
    public int ReferenceNumber { get; set; }
    [JsonConverter(typeof(LastArrayItemJsonConverter<DateTime>), "dd-MM-yyyy\\THH:mm:ss")]
    public DateTime BookingDate { get; set; }
}

Now, the model's properties will be populated with the last values from the arrays. Deserialize the json using:

var booking = JsonConvert.DeserializeObject<Booking>(json)

CodePudding user response:

You can define a single Booking class which includes

  • the BookingInput's properties
  • and the last item retrieval logics as well
public class Booking
{
    [JsonIgnore]
    public string BookingCode => BookingCodes.Last();
    [JsonIgnore]
    public int ReferenceNumber => ReferenceNumbers.Last();
    [JsonIgnore]
    public DateTime BookingDate => BookingDates.Last();

    [JsonProperty("bookingcode")]
    public List<string> BookingCodes { get; set; } = new List<string>();
    [JsonProperty("referencenumber")]
    public List<int> ReferenceNumbers { get; set; } = new List<int>();
    [JsonProperty("bookingdate")]
    public List<DateTime> BookingDates { get; set; } = new List<DateTime>();
}

CodePudding user response:

if you want to use your custom c# class

    using Newtonsoft.Json;

    var jsonParsed = JObject.Parse(json);

    foreach (var arr in jsonParsed.Properties())
        jsonParsed[arr.Name] = arr.Value.LastOrDefault();

    Booking booking = jsonParsed.ToObject<Booking>();

but since you can have up to 75 properties, maybe it would be better to use a dictionary instead of a custom class

    Dictionary<string, object> bookingDict = new Dictionary<string, object>();

    foreach (var arr in jsonParsed.Properties())
        bookingDict.Add(arr.Name, arr.Value.LastOrDefault());

result

{
  "bookingcode": "TEST",
  "referencenumber": 456,
  "bookingdate": "23-11-2022T17:00:25"
}

CodePudding user response:

It's better if you provide your request code too. did you add ReadAsStringAsync(); after the request?

public class Booking
{   
    [JsonProperty("bookingcode")]
    public List<string> BookingCodes { get; set; } = new List<string>();
  
    [JsonProperty("referencenumber")]
    public List<int> ReferenceNumbers { get; set; } = new List<int>();
    
    [JsonProperty("bookingdate")]
    public List<DateTime> BookingDates { get; set; } = new List<DateTime>();
}

And some web request sample

 using (var client = new HttpClient())
            {
                var response = await client.
                    GetAsync(apiEndpoint);
                var responseText = await response.Content.ReadAsStringAsync();
                var compareResult = JsonSerializer.Deserialize<Booking>(responseText)??new Booking();
                return compareResult;
            }
  • Related