diff --git a/src/Meilisearch/DefaultFormattable.cs b/src/Meilisearch/DefaultFormattable.cs
new file mode 100644
index 00000000..b31fbd6f
--- /dev/null
+++ b/src/Meilisearch/DefaultFormattable.cs
@@ -0,0 +1,34 @@
+using System.Text.Json.Serialization;
+
+namespace Meilisearch
+{
+ ///
+ /// The default implementation of
+ ///
+ ///
+ ///
+ public class DefaultFormattable : IFormatContainer
+ {
+ ///
+ /// Creates a formatted document
+ ///
+ ///
+ ///
+ public DefaultFormattable(TOriginal original, TFormatted formatted)
+ {
+ Original = original;
+ Formatted = formatted;
+ }
+
+ ///
+ /// The original document
+ ///
+ public TOriginal Original { get; }
+
+ ///
+ /// The formatted document
+ ///
+ [JsonPropertyName("_formatted")]
+ public TFormatted Formatted { get; }
+ }
+}
diff --git a/src/Meilisearch/IFormatContainer.cs b/src/Meilisearch/IFormatContainer.cs
new file mode 100644
index 00000000..52c1b503
--- /dev/null
+++ b/src/Meilisearch/IFormatContainer.cs
@@ -0,0 +1,23 @@
+using System.Text.Json.Serialization;
+
+namespace Meilisearch
+{
+ ///
+ /// Used to receive document formatting information
+ ///
+ /// Original data type
+ /// Formatted data type
+ [JsonConverter(typeof(IFormatContainerJsonConverterFactory))]
+ public interface IFormatContainer
+ {
+ ///
+ /// The original result
+ ///
+ TOriginal Original { get; }
+
+ ///
+ /// The formatted result
+ ///
+ TFormatted Formatted { get; }
+ }
+}
diff --git a/src/Meilisearch/IFormatContainerJsonConverter.cs b/src/Meilisearch/IFormatContainerJsonConverter.cs
new file mode 100644
index 00000000..55196706
--- /dev/null
+++ b/src/Meilisearch/IFormatContainerJsonConverter.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Meilisearch
+{
+ public class IFormatContainerJsonConverterFactory : JsonConverterFactory
+ {
+ public override bool CanConvert(Type typeToConvert)
+ {
+ return typeToConvert.IsInterface
+ && typeToConvert.IsGenericType
+ && (typeToConvert.GetGenericTypeDefinition() == typeof(IFormatContainer<,>));
+ }
+
+ public override JsonConverter CreateConverter(
+ Type typeToConvert,
+ JsonSerializerOptions options
+ )
+ {
+ var genericArgs = typeToConvert.GetGenericArguments();
+ var converterType = typeof(IFormatContainerJsonConverter<,>).MakeGenericType(
+ genericArgs[0],
+ genericArgs[1]
+ );
+ var converter = (JsonConverter)Activator.CreateInstance(converterType);
+ return converter;
+ }
+ }
+
+ public class IFormatContainerJsonConverter
+ : JsonConverter>
+ where TFormatted : class
+ where TOriginal : class
+ {
+ public override IFormatContainer Read(
+ ref Utf8JsonReader reader,
+ Type typeToConvert,
+ JsonSerializerOptions options
+ )
+ {
+ var document = JsonSerializer.Deserialize(ref reader, options);
+ var original = document.Deserialize(options);
+
+ if (document.TryGetProperty("_formatted", out var formattedElement))
+ {
+ var formatted = formattedElement.Deserialize(options);
+ return new DefaultFormattable(original, formatted);
+ }
+ return new DefaultFormattable(original, null);
+ }
+
+ public override void Write(
+ Utf8JsonWriter writer,
+ IFormatContainer value,
+ JsonSerializerOptions options
+ )
+ {
+ var serialized = JsonSerializer.SerializeToNode(value.Original, options).AsObject();
+ serialized["_formatted"] = JsonSerializer.SerializeToNode(value.Formatted, options);
+ serialized.WriteTo(writer, options);
+ }
+ }
+}
diff --git a/src/Meilisearch/Index.Documents.cs b/src/Meilisearch/Index.Documents.cs
index 88f35761..06167aab 100644
--- a/src/Meilisearch/Index.Documents.cs
+++ b/src/Meilisearch/Index.Documents.cs
@@ -456,16 +456,8 @@ public async Task DeleteAllDocumentsAsync(CancellationToken cancellati
.ConfigureAwait(false);
}
- ///
- /// Search documents according to search parameters.
- ///
- /// Query Parameter with Search.
- /// Attributes to search.
- /// The cancellation token for this call.
- /// Type parameter to return.
- /// Returns Enumerable of items.
- public async Task> SearchAsync(string query,
- SearchQuery searchAttributes = default(SearchQuery), CancellationToken cancellationToken = default)
+ private async Task<(SearchQuery, HttpResponseMessage)> SendSearchRequest(string query,
+ SearchQuery searchAttributes = default, CancellationToken cancellationToken = default)
{
SearchQuery body;
if (searchAttributes == null)
@@ -483,9 +475,54 @@ public async Task DeleteAllDocumentsAsync(CancellationToken cancellati
Constants.JsonSerializerOptionsRemoveNulls, cancellationToken: cancellationToken)
.ConfigureAwait(false);
- return await responseMessage.Content
- .ReadFromJsonAsync>(cancellationToken: cancellationToken)
+ return (body, responseMessage);
+ }
+
+ ///
+ /// Search documents according to search parameters.
+ ///
+ /// Query Parameter with Search.
+ /// Attributes to search.
+ /// The cancellation token for this call.
+ /// Type parameter to return.
+ /// Returns Enumerable of items.
+ public async Task> SearchAsync(string query,
+ SearchQuery searchAttributes = default, CancellationToken cancellationToken = default)
+ {
+
+ var (body, responseMessage) = await SendSearchRequest(query, searchAttributes, cancellationToken);
+
+ return body.Page != null || body.HitsPerPage != null
+ ? await responseMessage.Content
+ .ReadFromJsonAsync>(cancellationToken: cancellationToken)
+ .ConfigureAwait(false)
+ : (ISearchable)await responseMessage.Content
+ .ReadFromJsonAsync>(cancellationToken: cancellationToken)
.ConfigureAwait(false);
}
+
+ ///
+ /// Search documents according to search parameters, Including format.
+ ///
+ /// Query Parameter with Search.
+ /// Attributes to search.
+ /// The cancellation token for this call.
+ /// Type parameter to return.
+ /// formatted document type.
+ /// Returns Enumerable of items.
+ public async Task>> SearchAsync(string query,
+ SearchQuery searchAttributes = default, CancellationToken cancellationToken = default)
+ {
+ var (body, responseMessage) = await SendSearchRequest(query, searchAttributes, cancellationToken);
+
+ return body.Page != null || body.HitsPerPage != null
+ ? await responseMessage.Content
+ .ReadFromJsonAsync>>(cancellationToken: cancellationToken)
+ .ConfigureAwait(false)
+ : (ISearchable>)await responseMessage.Content
+ .ReadFromJsonAsync>>(cancellationToken: cancellationToken)
+ .ConfigureAwait(false);
+ }
+
}
}
diff --git a/tests/Meilisearch.Tests/Movie.cs b/tests/Meilisearch.Tests/Movie.cs
index fe279502..5a92c7e7 100644
--- a/tests/Meilisearch.Tests/Movie.cs
+++ b/tests/Meilisearch.Tests/Movie.cs
@@ -34,16 +34,4 @@ public class MovieWithIntId
public string Genre { get; set; }
}
-
- public class FormattedMovie
- {
- public string Id { get; set; }
-
- public string Name { get; set; }
-
- public string Genre { get; set; }
-
-#pragma warning disable SA1300
- public Movie _Formatted { get; set; }
- }
}
diff --git a/tests/Meilisearch.Tests/SearchTests.cs b/tests/Meilisearch.Tests/SearchTests.cs
index 19633a93..68539e9d 100644
--- a/tests/Meilisearch.Tests/SearchTests.cs
+++ b/tests/Meilisearch.Tests/SearchTests.cs
@@ -1,4 +1,5 @@
using System.Linq;
+using System.Text.Json;
using System.Threading.Tasks;
using FluentAssertions;
@@ -32,6 +33,33 @@ public async Task InitializeAsync()
public Task DisposeAsync() => Task.CompletedTask;
+ [Fact]
+ public async Task TestJsonConverter()
+ {
+ MovieWithIntId[] movies =
+ {
+ new MovieWithIntId { Id = 1, Name = "Batman" },
+ new MovieWithIntId { Id = 2, Name = "Reservoir Dogs" },
+ new MovieWithIntId { Id = 3, Name = "Taxi Driver" },
+ new MovieWithIntId { Id = 4, Name = "Interstellar" },
+ new MovieWithIntId { Id = 5, Name = "Titanic" },
+ }; ;
+ var formattable = movies
+ .Select(x => new DefaultFormattable(x, new Movie()
+ {
+ Id = x.Id.ToString(),
+ Genre = x.Genre,
+ Name = x.Name
+ }))
+ .Cast>();
+
+ var elements = formattable.Select(x => JsonSerializer.SerializeToElement(x)).ToList();
+ elements.Should().AllSatisfy(x => x.GetProperty("_formatted").Should().NotBeNull());
+
+ var deserialized = elements.Select(x => JsonSerializer.Deserialize>(x)).ToList();
+ deserialized.Should().AllSatisfy(x => x.Formatted.Should().NotBeNull());
+ }
+
[Fact]
public async Task BasicSearch()
{
@@ -107,46 +135,46 @@ public async Task CustomSearchWithAttributesToHighlight()
task.TaskUid.Should().BeGreaterOrEqualTo(0);
await _basicIndex.WaitForTaskAsync(task.TaskUid);
- var movies = await _basicIndex.SearchAsync(
+ var movies = await _basicIndex.SearchAsync(
"man",
new SearchQuery { AttributesToHighlight = new string[] { "name" } });
movies.Hits.Should().NotBeEmpty();
- movies.Hits.First().Id.Should().NotBeEmpty();
- movies.Hits.First().Name.Should().NotBeEmpty();
- movies.Hits.First().Genre.Should().NotBeEmpty();
- movies.Hits.First()._Formatted.Name.Should().NotBeEmpty();
+ movies.Hits.First().Original.Id.Should().NotBeEmpty();
+ movies.Hits.First().Original.Name.Should().NotBeEmpty();
+ movies.Hits.First().Original.Genre.Should().NotBeEmpty();
+ movies.Hits.First().Formatted.Name.Should().NotBeEmpty();
}
[Fact]
public async Task CustomSearchWithNoQuery()
{
- var movies = await _basicIndex.SearchAsync(
+ var movies = await _basicIndex.SearchAsync(
null,
new SearchQuery { AttributesToHighlight = new string[] { "name" } });
movies.Hits.Should().NotBeEmpty();
- movies.Hits.First().Id.Should().NotBeNull();
- movies.Hits.First().Name.Should().NotBeNull();
- movies.Hits.First()._Formatted.Id.Should().NotBeNull();
- movies.Hits.First()._Formatted.Name.Should().NotBeNull();
+ movies.Hits.First().Original.Id.Should().NotBeNull();
+ movies.Hits.First().Original.Name.Should().NotBeNull();
+ movies.Hits.First().Formatted.Id.Should().NotBeNull();
+ movies.Hits.First().Formatted.Name.Should().NotBeNull();
}
[Fact]
public async Task CustomSearchWithEmptyQuery()
{
- var movies = await _basicIndex.SearchAsync(
+ var movies = await _basicIndex.SearchAsync(
string.Empty,
new SearchQuery { AttributesToHighlight = new string[] { "name" } });
movies.Hits.Should().NotBeEmpty();
- movies.Hits.First().Id.Should().NotBeNull();
- movies.Hits.First().Name.Should().NotBeNull();
- movies.Hits.First()._Formatted.Id.Should().NotBeNull();
- movies.Hits.First()._Formatted.Name.Should().NotBeNull();
+ movies.Hits.First().Original.Id.Should().NotBeNull();
+ movies.Hits.First().Original.Name.Should().NotBeNull();
+ movies.Hits.First().Formatted.Id.Should().NotBeNull();
+ movies.Hits.First().Formatted.Name.Should().NotBeNull();
}
[Fact]
public async Task CustomSearchWithMultipleOptions()
{
- var movies = await _basicIndex.SearchAsync(
+ var movies = await _basicIndex.SearchAsync(
"man",
new SearchQuery
{
@@ -158,12 +186,12 @@ public async Task CustomSearchWithMultipleOptions()
Assert.NotEmpty(movies.Hits);
Assert.Single(movies.Hits);
- Assert.NotEmpty(firstHit.Name);
- Assert.NotEmpty(firstHit.Id);
- Assert.Null(firstHit.Genre);
- Assert.NotEmpty(firstHit._Formatted.Name);
- Assert.Equal("15", firstHit._Formatted.Id);
- Assert.Null(firstHit._Formatted.Genre);
+ Assert.NotEmpty(firstHit.Original.Name);
+ Assert.NotEmpty(firstHit.Original.Id);
+ Assert.Null(firstHit.Original.Genre);
+ Assert.NotEmpty(firstHit.Formatted.Name);
+ Assert.Equal("15", firstHit.Formatted.Id);
+ Assert.Null(firstHit.Formatted.Genre);
}
[Fact]
@@ -363,31 +391,31 @@ public async Task CustomSearchWithSort()
[Fact]
public async Task CustomSearchWithCroppingParameters()
{
- var movies = await _basicIndex.SearchAsync(
+ var movies = await _basicIndex.SearchAsync(
"man",
new SearchQuery { CropLength = 1, AttributesToCrop = new string[] { "*" } }
);
Assert.NotEmpty(movies.Hits);
- Assert.Equal("…Man", movies.Hits.First()._Formatted.Name);
+ Assert.Equal("…Man", movies.Hits.First().Formatted.Name);
}
[Fact]
public async Task CustomSearchWithCropMarker()
{
- var movies = await _basicIndex.SearchAsync(
+ var movies = await _basicIndex.SearchAsync(
"man",
new SearchQuery { CropLength = 1, AttributesToCrop = new string[] { "*" }, CropMarker = "[…] " }
);
Assert.NotEmpty(movies.Hits);
- Assert.Equal("[…] Man", movies.Hits.First()._Formatted.Name);
+ Assert.Equal("[…] Man", movies.Hits.First().Formatted.Name);
}
[Fact]
public async Task CustomSearchWithCustomHighlightTags()
{
- var movies = await _basicIndex.SearchAsync(
+ var movies = await _basicIndex.SearchAsync(
"man",
new SearchQuery
{
@@ -398,7 +426,7 @@ public async Task CustomSearchWithCustomHighlightTags()
);
Assert.NotEmpty(movies.Hits);
- Assert.Equal("Iron Man", movies.Hits.First()._Formatted.Name);
+ Assert.Equal("Iron Man", movies.Hits.First().Formatted.Name);
}
[Fact]