0

I have an json string which contains the following data

"air:FlightOptionsList": {
    "air:FlightOption": [{
            "LegRef": "hx5kk+3R2BKABGzqAAAAAA==",
            "Destination": "LHE",
            "Origin": "DXB",
            "air:Option": {
                "Key": "hx5kk+3R2BKA/FzqAAAAAA==",
                "TravelTime": "P0DT3H0M0S",
                "air:BookingInfo": {
                    "BookingCode": "I",
                    "BookingCount": "7",
                    "CabinClass": "Economy",
                    "FareInfoRef": "hx5kk+3R2BKAzFzqAAAAAA==",
                    "SegmentRef": "hx5kk+3R2BKAtFzqAAAAAA=="
                }
            }
        }, {
            "LegRef": "hx5kk+3R2BKAFGzqAAAAAA==",
            "Destination": "DXB",
            "Origin": "LHE",
            "air:Option": {
                "Key": "hx5kk+3R2BKACGzqAAAAAA==",
                "TravelTime": "P0DT11H30M0S",
                "air:BookingInfo": [{
                        "BookingCode": "U",
                        "BookingCount": "7",
                        "CabinClass": "Economy",
                        "FareInfoRef": "hx5kk+3R2BKA+FzqAAAAAA==",
                        "SegmentRef": "hx5kk+3R2BKAvFzqAAAAAA=="
                    }, {
                        "BookingCode": "Y",
                        "BookingCount": "9",
                        "CabinClass": "Economy",
                        "FareInfoRef": "hx5kk+3R2BKA+FzqAAAAAA==",
                        "SegmentRef": "hx5kk+3R2BKAxFzqAAAAAA=="
                    }
                ],
                "air:Connection": {
                    "SegmentIndex": "0"
                }
            }
        }
    ]
}

my Class Structure is below:

public class FlightOptionsList
{
    public List<FlightOption> FlightOption { get; set; }
}

public class FlightOption
{
    public string LegRef { get; set; }
    public string Destination { get; set; }
    public string Origin { get; set; }
    public Option Option { get; set; }
}

public class Option
{
    public string Key { get; set; }
    public string TravelTime { get; set; }
    public List<BookingInfo> BookingInfo { get; set; }
    public Connection Connection { get; set; }
}

public class BookingInfo
{
    public string BookingCode { get; set; }
    public string BookingCount { get; set; }
    public string CabinClass { get; set; }
    public string FareInfoRef { get; set; }
    public string SegmentRef { get; set; }
}

I want to deserialize it, but its giving me an error as following:

Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[ParseSoapEnveloperReqRes.BookingInfo]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly. To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) 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 'FlightOptionsList.FlightOption[0].Option.BookingInfo.BookingCode', line 394, position 59.

this is because, as if you see the json string, FlightOptionsList.FlightOption[0].Option.BookingInfo is an object but in FlightOptionsList.FlightOption[1].Option.BookingInfo is an array as you can see.

how can I set this problem... I am using the following code to deserialize the json string to class object

 var AirTravelResultModel = JsonConvert.DeserializeObject<AirTravelResultModel>(xmlInputData);
Saadi
  • 2,211
  • 4
  • 21
  • 50
Darab Javaid
  • 145
  • 2
  • 13
  • Your class structure doesn't match your JSON but you don't show us that so we can't really help... – DavidG Nov 12 '17 at 13:33
  • let me just add the class structure – Darab Javaid Nov 12 '17 at 13:34
  • just check it now – Darab Javaid Nov 12 '17 at 13:37
  • Can you share your class `AirTravelResultModel`? – Saadi Nov 12 '17 at 13:39
  • Why is your variable called `xmlInputData` when you claim it is JSON? – DavidG Nov 12 '17 at 13:40
  • the whole AirTravelResultModel is very big that's why i gave this part where the error is generating, xmlInputData is just the variable name – Darab Javaid Nov 12 '17 at 13:41
  • 1
    The Problem is, that the JSON is malformed. the first appearance of "air:BookingInfo" is a single object, the next one is an array. To fix that, the fist appearance of "air:BookingInfo" should also be wrapped in square brackets. – thmshd Nov 12 '17 at 13:43
  • There's just so many things wrong here. First, we need to see your `AirTravelResultModel` as that is what you are deserialising into. If it's too big, then it can't possibly be relevant to the JSON you have posted. Second, the JSON isn't even valid – DavidG Nov 12 '17 at 13:43
  • I converted it from XML string using 'strJson = JsonConvert.SerializeObject(doc);' – Darab Javaid Nov 12 '17 at 13:44
  • 2
    Your JSON structure is inconsistent. In the first element in `air:FlightOption`, `air:BookingInfo` is an object and in the second one, `air:BookingInfo` is an array. Based on your class structure, JSON.NET is expecting it to be an array. It looks like you're creating this JSON from XML and that's resulting in the inconsistent strucuture. Why not just deserialize from XML -> .NET classes? Why are you converting XML -> JSON just so that you can then convert JSON -> .NET? – JLRishe Nov 12 '17 at 13:44
  • @JLRishe But it would be valid from JSON rules, just not easy to deserialize. You need a custom converter, thats all – Sir Rufo Nov 12 '17 at 13:46
  • @SirRufo I never said OP's JSON is invalid. I said it was inconsistent and that what OP is doing is counterproductive. – JLRishe Nov 12 '17 at 13:58
  • Sometimes a user has no control over the json coming in. "Fixing the json" or object is one solution. Unfortunately, some companies just store objects as lists or objects depending on whether there is one or many. I have this exact problem as well, I am also converting poorly formed XML to JSON which can result in his error because JSON is easier to work with. So I am upvoting this question, because it is valid, ignoring possible object errors. I expanded upon https://stackoverflow.com/questions/5224697/deserializing-json-when-sometimes-array-and-sometimes-object for newtonsoft. – Patrick Knott Jul 07 '20 at 15:30

3 Answers3

7

Look at Camilo Martinez answer on this discussion : Deserializing JSON when sometimes array and sometimes object

Basically, you'll need to add the JsonConverter attribute to your BookingInfo property and handle the conversion in a JsonConverter implementation.

public class FlightOptionsList
{
    public List<FlightOption> FlightOption { get; set; }
}

public class FlightOption
{
    public string LegRef { get; set; }
    public string Destination { get; set; }
    public string Origin { get; set; }
    public Option Option { get; set; }
}

public class Option
{
    public string Key { get; set; }
    public string TravelTime { get; set; }
    [JsonConverter(typeof(SingleValueArrayConverter<BookingInfo>))]
    public List<BookingInfo> BookingInfo { get; set; }
    public Connection Connection { get; set; }
}

public class BookingInfo
{
    public string BookingCode { get; set; }
    public string BookingCount { get; set; }
    public string CabinClass { get; set; }
    public string FareInfoRef { get; set; }
    public string SegmentRef { get; set; }
}

public class Connection
{
    public string SegmentIndex { get; set; }
}

And here's the converter :

public class SingleValueArrayConverter<T> : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        object retVal = new Object();
        if (reader.TokenType == JsonToken.StartObject)
        {
            T instance = (T)serializer.Deserialize(reader, typeof(T));
            retVal = new List<T>() { instance };
        }
        else if (reader.TokenType == JsonToken.StartArray)
        {
            retVal = serializer.Deserialize(reader, objectType);
        }
        return retVal;
    }

    public override bool CanConvert(Type objectType)
    {
        return true;
    }
}
Roger Leblanc
  • 1,543
  • 1
  • 10
  • 9
  • This solution works fine with deserializing, but if I try to send the model back to user using **return Json(AirTravelResultModel)** , it gives **"ExceptionMessage": "Token PropertyName in state Property would result in an invalid JSON object. Path 'LowFareSearchRsp.AirPricePointList.AirPricePoint.AirPricingInfo[0]'."** – Darab Javaid Nov 12 '17 at 15:24
  • 1
    Can you give more details as I have no clue what is LowFareSearchRsp object or Json() method you're calling. – Roger Leblanc Nov 12 '17 at 16:07
0

Most of the text in this answer is text from your question with a few tweaks. All you need to do is to fix your JSON structure, and decorate the classes with JsonProperty attribute because your JSON property names have air: in it and : is not allowed in C#. That's all.

Step 1

First thing you need to do fix is your JSON because in one case air:BookingInfo is an object and then in the other case, it is an array. Therefore, its schema is not consistent. The correct JSON would be like this:

{
    "air:FlightOption": [{
            "LegRef": "hx5kk+3R2BKABGzqAAAAAA==",
            "Destination": "LHE",
            "Origin": "DXB",
            "air:Option": {
                "Key": "hx5kk+3R2BKA/FzqAAAAAA==",
                "TravelTime": "P0DT3H0M0S",
                "air:BookingInfo": [{
                    "BookingCode": "I",
                    "BookingCount": "7",
                    "CabinClass": "Economy",
                    "FareInfoRef": "hx5kk+3R2BKAzFzqAAAAAA==",
                    "SegmentRef": "hx5kk+3R2BKAtFzqAAAAAA=="
                }]
            }
        },
        {
            "LegRef": "hx5kk+3R2BKAFGzqAAAAAA==",
            "Destination": "DXB",
            "Origin": "LHE",
            "air:Option": {
                "Key": "hx5kk+3R2BKACGzqAAAAAA==",
                "TravelTime": "P0DT11H30M0S",
                "air:BookingInfo": [{
                        "BookingCode": "U",
                        "BookingCount": "7",
                        "CabinClass": "Economy",
                        "FareInfoRef": "hx5kk+3R2BKA+FzqAAAAAA==",
                        "SegmentRef": "hx5kk+3R2BKAvFzqAAAAAA=="
                    },
                    {
                        "BookingCode": "Y",
                        "BookingCount": "9",
                        "CabinClass": "Economy",
                        "FareInfoRef": "hx5kk+3R2BKA+FzqAAAAAA==",
                        "SegmentRef": "hx5kk+3R2BKAxFzqAAAAAA=="
                    }
                ],
                "air:Connection": {
                    "SegmentIndex": "0"
                }
            }
        }
    ]
}

Step 2

You can create the classes for your JSON easily using Visual Studio as I have shown in this answer.

Step 3

You need to help the serializer a little bit and tell it how to map the JSON properties to your class's properties and for that you can use JsonPropertyAttribute attribute as shown below:

public class FlightOptionsList
{
    [JsonProperty("air:FlightOption")]
    public AirFlightoption[] airFlightOption { get; set; }
}

public class AirFlightoption
{
    public string LegRef { get; set; }
    public string Destination { get; set; }
    public string Origin { get; set; }
    [JsonProperty("air:Option")]
    public AirOption airOption { get; set; }
}

public class AirOption
{
    public string Key { get; set; }
    public string TravelTime { get; set; }
    [JsonProperty("air:BookingInfo")]
    public AirBookingInfo[] airBookingInfo { get; set; }
    [JsonProperty("air:Connection")]
    public AirConnection airConnection { get; set; }
}

public class AirBookingInfo
{
    public string BookingCode { get; set; }
    public string BookingCount { get; set; }
    public string CabinClass { get; set; }
    public string FareInfoRef { get; set; }
    public string SegmentRef { get; set; }
}

public class AirConnection
{
    public string SegmentIndex { get; set; }
}

Step 4

The only code you need is this:

var fol = JsonConvert.DeserializeObject<FlightOptionsList>("YourJSON");
CodingYoshi
  • 25,467
  • 4
  • 62
  • 64
0

I apologize for the redundancy of posting this twice... unfortunately this question is somewhat resolved in its predecessor: Deserializing JSON when sometimes array and sometimes object. I will not use your objects. Ensure your objects are well formed. Sometimes companies may store an object as an array or object depending on whether there is one or many. You may be running into that. Ensure that your converter is returning a list or an array pending your object type. In my instance I needed to return an array.

Expounding upon Martinez and mfanto's answer for Newtonsoft. Their answer does work with Newtonsoft:

Here is an example of doing it with an array instead of a list (and correctly named).

public class SingleValueArrayConverter<T> : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.StartObject 
            || reader.TokenType == JsonToken.String
            || reader.TokenType == JsonToken.Integer)
        {
            return new T[] { serializer.Deserialize<T>(reader) };
        }
        return serializer.Deserialize<T[]>(reader);
    }

    public override bool CanConvert(Type objectType)
    {
        return true;
    }
}

Then over the attribute write this:

[JsonProperty("INSURANCE")]
[JsonConverter(typeof(SingleValueArrayConverter<InsuranceInfo>))]
public InsuranceInfo[] InsuranceInfo { get; set; }

Newtonsoft will do the rest for you.

return JsonConvert.DeserializeObject<T>(json);

Cheers to Martinez and mfanto!

Believe it or not, this will work with sub items. (It may even have to.) So... inside of my InsuranceInfo, if I have another object/array hybrid, use this again on that property like so:

[JsonProperty("CLAIM")]
[JsonConverter(typeof(SingleValueArrayConverter<Claim>))]
public Claim[] ClaimInfo { get; set; }

Due to implementing WriteJson, this will also allow for reserializing the object back to json if necessary, but at that point, it will always be an array (yay!).

Patrick Knott
  • 1,666
  • 15
  • 15