Reputation: 396
The API I have to work with returns an object when there would be only one entry in the array, a proper array otherwise. Here's an example:
Only one entry:
{
"product": {
"offers": {
"id": 1,
"price": 55.6
}
}
}
More than one entry:
{
"product": {
"offers": [
{
"id": 1,
"price": 55.6
},
{
"id": 2,
"price": 34.6
},
]
}
}
Is there some way to write code that deserializes both of those variants into arrays without writing a full JsonConverter
for the whole (deeply nested) response? Besides this weird seeming design decision, JSON.net can easily deserialize it, so I'm wondering if there is JSON.net feature that allows me to write something like this:
if (hasExpectedArray && hasEncounteredObject) {
deserialized.property = new List<T>();
deserialized.property.Add(objectEncountered);
}
My other idea would be to preparse the JSON and using some search and replace functionality to change all objects that can be arrays to arrays. But that seems dirty and brittle.
edit: actually forgot about the third case: it can also be a string or one of the array members can be a string:
{
"product": {
"offers": "This is an offer"
}
}
{
"product": {
"offers": [
{
"id": 1,
"price": 55.6
},
"This is another offer"
]
}
}
Upvotes: 3
Views: 1868
Reputation: 243
I had similar problem, "value": {..} was in some case returns as "value":[{},..]
The structure was pretty simple and not too deep, so instead of writing a serialize, i did a simple string replace
"value":[ replace with "values":[. That way, simple values was name value, and multi values was name values.
The drawback is ofcause the extra parsing, and doing convert over a string replace, but it was a simple way to solve handling an external api using bad json formats
Upvotes: 0
Reputation: 396
Shortly after posting this I found a blogpost further down in the search results that solves this exact problem:
http://michaelcummings.net/mathoms/using-a-custom-jsonconverter-to-fix-bad-json-results/
edit 2017-07-19: Another blogpost popped up: http://trycatchfail.com/blog/post/Dealing-with-Horrid-No-Good-Very-Bad-APIs-Using-JSONNET
In case it ever goes down, here is the relevant code for a JsonConverter:
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;
}
edit:
What I ended up doing was adding an interface:
public interface IHasContent
{
string Content { get; set; }
}
as at least the string only types always had the same name for those.
the problematic types got the attribute and interface:
[JsonConverter(typeof(SingleValueArrayConverter<ShippingInfo>))]
public class ShippingInfo : IHasContent
For the string conversion I added a simple implicit operator:
public static implicit operator ShippingInfo(string s)
{
return new ShippingInfo { Content = s };
}
The converter got some type constraints:
public class SingleValueArrayConverter<T> : JsonConverter where T : IHasContent, new()
and finally, here is the ReadJson method:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object retVal;
switch (reader.TokenType)
{
case JsonToken.StartObject:
var instance = (T)serializer.Deserialize(reader, typeof(T));
retVal = new List<T> { instance };
break;
case JsonToken.StartArray:
retVal = serializer.Deserialize(reader, objectType);
break;
case JsonToken.String:
retVal = ReadStringAsContentObject();
break;
default:
throw new ArgumentException();
}
return retVal;
object ReadStringAsContentObject()
{
var content = new T();
content.Content = reader.ReadAsString();
var returnObject = new List<T> { content };
return returnObject;
}
}
Upvotes: 8