How to consume/deserialize strapi v4 endpoints in C#?

Hello :slight_smile: I have a solution that proposes a System.Text.Json JsonConverter. It is using reflection, so I would not recommend using this code other than on a WebApi solution. Reflection may be slow on mobile platforms.

Solution

    // An abstract class for your items
    public abstract class StrapiItem<T>
        where T : StrapiItem<T>
    {
        public int Id { get; set; }

        public string Locale { get; set; }

        public DateTime? CreatedAt { get; set; }

        public DateTime? UpdatedAt { get; set; }

        public DateTime? PublishedAt { get; set; }

        public StrapiListWrapper<T> Localizations { get; set; }
    }
    // A wrapper for your lists
    public class StrapiListWrapper<T>
        where T : StrapiItem<T>
    {
        public IEnumerable<T> Data { get; set; }

        public Meta Meta { get; set; }
    }
    // A JsonConverter implementation for a StrapiItem<T>
    public class StrapiItemConverter<T> : JsonConverter<T>
        where T : StrapiItem<T>, new()
    {
        public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            string? propertyName = null;
            var item = new T();
            var properties = typeToConvert.GetProperties();
            var initialDepth = reader.CurrentDepth;
            while (reader.Read())
            {
                if (reader.TokenType == JsonTokenType.PropertyName)
                {
                    propertyName = reader.GetString();
                }
                else if (!string.IsNullOrWhiteSpace(propertyName))
                {
                    if (propertyName == "attributes" || reader.TokenType == JsonTokenType.Null)
                    {
                        continue;
                    }

                    var property = properties.SingleOrDefault(property => property.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase));
                    var setMethod = property?.GetSetMethod();
                    if (setMethod != null)
                    {
                        object? value = null;
                        if (typeof(int).IsAssignableTo(property!.PropertyType))
                        {
                            value = reader.GetInt32();
                        }
                        else if (typeof(string).IsAssignableTo(property!.PropertyType))
                        {
                            value = reader.GetString();
                        }
                        else if (typeof(DateTime).IsAssignableTo(property!.PropertyType))
                        {
                            value = reader.GetDateTime();
                        }
                        else if (typeof(DateTimeOffset).IsAssignableTo(property!.PropertyType))
                        {
                            value = reader.GetDateTimeOffset();
                        }
                        else if (typeof(bool).IsAssignableTo(property!.PropertyType))
                        {
                            value = reader.GetBoolean();
                        }
                        else if (typeof(decimal).IsAssignableTo(property!.PropertyType))
                        {
                            value = reader.GetDecimal();
                        }
                        else if (typeof(double).IsAssignableTo(property!.PropertyType))
                        {
                            value = reader.GetDouble();
                        }
                        else if (typeof(Guid).IsAssignableTo(property!.PropertyType))
                        {
                            value = reader.GetGuid();
                        }
                        else if (property.PropertyType.IsEnum || Nullable.GetUnderlyingType(property.PropertyType) != null)
                        {
                            var enumValue = reader.GetString();
                            if (!string.IsNullOrWhiteSpace(enumValue))
                            {
                                value = Enum.Parse(property.PropertyType, enumValue!, ignoreCase: true);
                            }
                        }
                        else
                        {
                            using (var jsonDocument = JsonDocument.ParseValue(ref reader))
                            {
                                var json = jsonDocument.RootElement.GetRawText();
                                value = JsonSerializer.Deserialize(json, property.PropertyType, options);
                            }
                        }

                        if (value != null)
                        {
                            setMethod.Invoke(item, new[] { value });
                        }
                    }

                    propertyName = null;
                }
                else if (reader.TokenType == JsonTokenType.EndObject && reader.CurrentDepth == initialDepth)
                {
                    break;
                }
            }

            return item;
        }

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

Usage

Single items

Now, when strapi returns a single item like this…

{
    "data": {
        "id": 1,
        "attributes": {
            "title": "foo",
            "createdAt": "2023-04-19T00:20:53.175Z",
            "updatedAt": "2023-04-21T17:28:45.603Z",
            "publishedAt": "2023-04-19T00:24:22.336Z",
            "locale": "en",
        }
    },
    "meta": {}
}

… you can deserialize it like this :

var jsonSerializerOptions = new JsonSerializerOptions
{
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    PropertyNameCaseInsensitive = true,
    WriteIndented = true,
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
var data = JsonSerializer.Deserialize<StrapiItemWrapper<Data>>(json, options);

And the data class would look something like this :

[JsonConverter(typeof(StrapiItemConverter<Data>))]
public class Data : StrapiItem<Data>
{
    public string Title { get; set; }
}

Lists

When strapi returns a list like this…

{
    "data": [
        {
            "id": 1,
            "attributes": {
                "title": "Mitre saws",
                "createdAt": "2023-04-19T00:20:53.175Z",
                "updatedAt": "2023-04-21T17:28:45.603Z",
                "publishedAt": "2023-04-19T00:24:22.336Z",
                "locale": "en",
            }
        }
    ]
}

…you can deserialize it like this :

var jsonSerializerOptions = new JsonSerializerOptions
{
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    PropertyNameCaseInsensitive = true,
    WriteIndented = true,
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
var data = JsonSerializer.Deserialize<StrapiListWrapper<Data>>(json, options);

I’m thinking of wrapping this into a nuget package or maybe make a pull request on this repo to help the .NET community use Strapi.

Let me know in the comments if you would be interested!

2 Likes