1

I'm consuming a REST API from an adventurous team. They're providing two endpoints where both return a similiar but not equal response. I'm deserializing the responses using the DataContractJsonSerializer.

Endpoint A response:

{
  "message": "Hello World."
}

Endpoint B response:

{
  "message": [
    "Hello World.",
    "Hello StackOverflow."
  ]
}

As you can see endpoint A provides a single string in the message property while endpoint B provides a string array.

I really really want to use the same DataContract but is there a way to make this happen?

[DataContract]
public class Response
{
  [DataMember(Name = "message")]
  public string Message { get; set; } // Changing this to string[] fails as well.
}

Of course I'm getting an error:

There was an error deserializing the object of type Response. End element 'message' from namespace '' expected. Found element 'item' from namespace ''.

For the sake of completion here's the code:

string jsonPayload = "{ 'Random': 'Payload' }";
HttpClient myHttpClient = getHttpClient();
HttpResponseMessage responseMsg = await myHttpClient.PostAsync("myApiPath", new StringContent(jsonPayload));

DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(Response));
string rspJson = await responseMsg.Content.ReadAsStringAsync();
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(rspJson));
Response rsp = (Response)serializer.ReadObject(ms);
dbc
  • 104,963
  • 20
  • 228
  • 340
Daniel Schmid
  • 362
  • 1
  • 5
  • 20
  • I doubt there are solutions that are easier then just using different classes for the response. Things i would have in mind would involve using different Json Serializers (the DataContract stuff is rather dead) then writing own ContractSerializers that can adapt behavior for the different Json types for the same name in the Json. Way more complicated then just using 2 different classes. – Ralf Jun 15 '23 at 13:17
  • Are you using .NET Framework or .NET Core? – dbc Jun 15 '23 at 15:55

1 Answers1

1

DataContractJsonSerializer has built-in support for polymorphic primitives, and arrays of primitives, so if you declare your Messages property as an object you will be able to deserialize either JSON:

[DataContract]
public class Response
{
    [DataMember(Name = "message")]
    public object Message { get; set; } // Changing this to string[] fails as well.
}

Demo fiddle #1 here.

This model doesn't really capture the fact that Message should be a string, or an array of strings, so you may instead prefer to use some surrogate property for serialization like so:

[DataContract]
public class Response
{
    [IgnoreDataMember]
    public string [] Messages { get; set; }
    
    [DataMember(Name = "message")]
    object SerializedMessages 
    { 
        get => Messages; 
        set => Messages = (value) switch
        {
            null => null,
            string s => new [] { s },
            string [] a => a,
            // Convert arrays of primitives to strings
            object [] a => a.Cast<IFormattable>().Select(f => Convert.ToString(f, CultureInfo.InvariantCulture)).ToArray(),
            _ => throw new ArgumentException(string.Format("Unknown value type {0}", value)),
        };
    }
}

Do note that, according to the docs

For most scenarios that involve serializing to JSON and deserializing from JSON, we recommend the APIs in the System.Text.Json namespace.

Demo fiddle #2 here.

dbc
  • 104,963
  • 20
  • 228
  • 340