4

I'm using System.Text.Json to deserialize some json. (More specifically, this call from the Last.fm API in json format)

The json that I'm trying to deserialize has a quite unconventional way of handling null values for some objects, for example when its null I get this:

    "tags": "",

And like this when it has values:

    "tags": {
        "tag": [
            {
                "name": "classic rock",
                "url": "https://www.last.fm/tag/classic+rock"
            },
            {
                "name": "rock",
                "url": "https://www.last.fm/tag/rock"
            }
        ]
    }

My C# class looks like this:

public class Artist
{
    public Tags Tags { get; set; }
}

public class Tags
{
    public Tag[] Tag { get; set; }
}

public class Tag
{
    public string Name { get; set; }
    public string Url { get; set; }
}

How would I check if an object is an empty string before actually trying to deserialize it?

When I try to deserialize it:

var deserializedObject = JsonSerializer.Deserialize<T>(requestBody);

It throws a System.Text.Json.JsonException: The JSON value could not be converted to FMBot.LastFM.Domain.Models.Tags. Path: $.artist.tags error.

This call used to work when the value was actually null, but now that it's an empty string it is broken and I can't figure out a solution.

Thom
  • 663
  • 7
  • 19
  • 1
    It's pretty bad form for a public API to fundamentally change like that - shame on last.fm. That being said, you will need to write a `JsonConverter` to explicitly handle this case. See: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to – Chris Pickford May 19 '21 at 12:48

2 Answers2

4

I would use a custom converter for this task. For example:

public class TagsConverter : JsonConverter<Tags>
{
    public override Tags Read(ref Utf8JsonReader reader, Type typeToConvert, 
        JsonSerializerOptions options)
    {
        // If the token is a string, return null
        if(reader.TokenType == JsonTokenType.String)
        {
            return null;
        }

        // Skip over the object and property name
        reader.Read(); // Object
        reader.Read(); // Property name

        // Read the tags array
        var tags = JsonSerializer.Deserialize<Tag[]>(ref reader, options);

        reader.Read(); // Object

        return new Tags { Tag = tags};
    }

    public override void Write(Utf8JsonWriter writer, Tags value, 
        JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }
}

And use it like this:

var options = new JsonSerializerOptions
{
    PropertyNameCaseInsensitive = true,
    Converters = { new TagsConverter() }
};

var deserializedObject = JsonSerializer.Deserialize<Artist>(requestBody, options );
DavidG
  • 113,891
  • 12
  • 217
  • 223
0

One simple way to solve this is by changing the type of the Tags property to dynamic.

public class Artist
{
    public dynamic Tags { get; set; }
}

You can then check if Tags is Tag[] or Tags is string and act accordingly. Don't forget to also check if it is neither of the above (and possibly throw).

BlokeTech
  • 317
  • 1
  • 7