Skip to content

Commit dd4c59b

Browse files
Query filter
1 parent 1c335c6 commit dd4c59b

File tree

4 files changed

+159
-13
lines changed

4 files changed

+159
-13
lines changed

Filter/QueryFilterConverter.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System.Text.Json;
2+
using System.Text.Json.Serialization;
3+
4+
namespace Infragistics.QueryBuilder.Executor.Filter
5+
{
6+
internal class QueryFilterConverter : System.Text.Json.Serialization.JsonConverter<QueryFilter>
7+
{
8+
public override QueryFilter Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
9+
{
10+
using var doc = JsonDocument.ParseValue(ref reader);
11+
return System.Text.Json.JsonSerializer.Deserialize<QueryFilter>(doc.RootElement.GetRawText(), options)!;
12+
}
13+
14+
public override void Write(Utf8JsonWriter writer, QueryFilter value, JsonSerializerOptions options)
15+
=> System.Text.Json.JsonSerializer.Serialize(writer, (object)value, value.GetType(), options);
16+
}
17+
}

Infragistics.QueryBuilder.Executor.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<ItemGroup>
2929
<PackageReference Include="AutoMapper" Version="14.0.0" />
3030
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
31+
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
3132
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="8.1.1" />
3233
<None Include="IgniteUI.png" Pack="true" PackagePath="" />
3334
<None Include="ReadMe.md">

Model/QueryFilter.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Infragistics.QueryBuilder.Executor
44
{
5-
public class QueryFilter
5+
public abstract class QueryFilter
66
{
77
// Basic condition
88
public string? FieldName { get; set; }
@@ -20,4 +20,8 @@ public class QueryFilter
2020

2121
public QueryFilter[] FilteringOperands { get; set; }
2222
}
23+
24+
public class FilteringExpressionsTree : QueryFilter { }
25+
26+
public class FilteringExpression : QueryFilter { }
2327
}

QueryExecutorExtensions.cs

Lines changed: 136 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,158 @@
1-
using Microsoft.AspNetCore.Builder;
1+
using System;
2+
using System.IO;
3+
using System.Text.Json.Nodes; // for JsonValue
4+
using Microsoft.AspNetCore.Builder;
25
using Microsoft.AspNetCore.Http;
3-
using Microsoft.AspNetCore.Mvc;
46
using Microsoft.AspNetCore.Routing;
57
using Microsoft.Extensions.DependencyInjection;
8+
using Newtonsoft.Json;
9+
using Newtonsoft.Json.Linq;
610

711
namespace Infragistics.QueryBuilder.Executor
812
{
913
public static class QueryExecutorExtensions
1014
{
11-
public static IServiceCollection AddQueryBuilder<TMyDbContext, TResults>(this IServiceCollection services) where TResults : class
15+
public static IServiceCollection AddQueryBuilder<TMyDbContext, TResults>(this IServiceCollection services)
16+
where TResults : class
1217
{
1318
services.AddScoped<QueryBuilderService<TMyDbContext, TResults>>();
1419
return services;
1520
}
1621

17-
public static IEndpointRouteBuilder UseQueryBuilder<TMyDbContext, TResults>(this IEndpointRouteBuilder endpoints, string path) where TMyDbContext : class where TResults : class
22+
public static IEndpointRouteBuilder UseQueryBuilder<TMyDbContext, TResults>(
23+
this IEndpointRouteBuilder endpoints, string path)
24+
where TMyDbContext : class
25+
where TResults : class
1826
{
19-
endpoints.MapPost(path, ([FromBody] Query query, QueryBuilderService<TMyDbContext, TResults> queryBuilderService) =>
27+
endpoints.MapPost(path, async (HttpContext ctx, QueryBuilderService<TMyDbContext, TResults> svc) =>
2028
{
21-
if (query != null)
29+
string json;
30+
using (var sr = new StreamReader(ctx.Request.Body))
2231
{
23-
var result = queryBuilderService.RunQuery(query);
24-
return Results.Ok(result);
32+
json = await sr.ReadToEndAsync();
2533
}
26-
else
34+
35+
var settings = new Newtonsoft.Json.JsonSerializerSettings
2736
{
37+
DateParseHandling = Newtonsoft.Json.DateParseHandling.None,
38+
ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore,
39+
};
40+
41+
// 1) Convert QueryFilter by shape (tree vs leaf)
42+
settings.Converters.Add(new QueryFilterCreationConverter());
43+
// 2) Let Newtonsoft write primitives (number/string/bool) into System.Text.Json.Nodes.JsonValue
44+
settings.Converters.Add(new NewtonsoftJsonValueConverter());
45+
46+
var query = Newtonsoft.Json.JsonConvert.DeserializeObject<Query>(json, settings);
47+
if (query is null)
2848
return Results.BadRequest("Wrong or missing query");
29-
}
30-
}).WithTags(["QueryBuilder"]).Accepts<Query>("application/json").Produces<TResults>();
49+
50+
var result = svc.RunQuery(query);
51+
return Results.Ok(result);
52+
})
53+
.WithTags(["QueryBuilder"])
54+
.Accepts<Query>("application/json")
55+
.Produces<TResults>();
56+
3157
return endpoints;
3258
}
3359
}
34-
}
60+
61+
/// <summary>
62+
/// Resolves QueryFilter to the only concrete types available:
63+
/// - FilteringExpressionsTree (has "filteringOperands")
64+
/// - FilteringExpression (leaf)
65+
/// </summary>
66+
file sealed class QueryFilterCreationConverter : Newtonsoft.Json.JsonConverter
67+
{
68+
private static readonly Type BaseType = typeof(QueryFilter);
69+
private static readonly Type TreeType = typeof(FilteringExpressionsTree);
70+
private static readonly Type LeafType = typeof(FilteringExpression);
71+
72+
public override bool CanConvert(Type objectType) => objectType == BaseType;
73+
74+
public override object ReadJson(
75+
Newtonsoft.Json.JsonReader reader,
76+
Type objectType,
77+
object? existingValue,
78+
Newtonsoft.Json.JsonSerializer serializer)
79+
{
80+
var jo = JObject.Load(reader);
81+
82+
// Tree if it has an array "filteringOperands"; otherwise leaf
83+
var isTree = jo["filteringOperands"] is JArray;
84+
var target = isTree ? TreeType : LeafType;
85+
86+
return jo.ToObject(target, serializer)!;
87+
}
88+
89+
public override void WriteJson(
90+
Newtonsoft.Json.JsonWriter writer,
91+
object? value,
92+
Newtonsoft.Json.JsonSerializer serializer)
93+
{
94+
serializer.Serialize(writer, value);
95+
}
96+
}
97+
98+
/// <summary>
99+
/// Newtonsoft -> System.Text.Json.Nodes.JsonValue bridge.
100+
/// Handles primitives and null (which is all your payload needs for "searchVal": 10253).
101+
/// </summary>
102+
file sealed class NewtonsoftJsonValueConverter : Newtonsoft.Json.JsonConverter
103+
{
104+
public override bool CanConvert(Type objectType)
105+
=> objectType == typeof(JsonValue);
106+
107+
public override object? ReadJson(
108+
Newtonsoft.Json.JsonReader reader,
109+
Type objectType,
110+
object? existingValue,
111+
Newtonsoft.Json.JsonSerializer serializer)
112+
{
113+
if (reader.TokenType == Newtonsoft.Json.JsonToken.Null)
114+
return null;
115+
116+
// Extract the raw .NET value from Newtonsoft token
117+
var token = JToken.ReadFrom(reader);
118+
119+
// For numbers/bools/strings, token.ToObject<object>() is a primitive -> wrap it
120+
// For safety, we only support primitives here since JsonValue is a primitive wrapper.
121+
// (Your payload uses numbers/strings/null for searchVal.)
122+
var primitive = token.Type switch
123+
{
124+
JTokenType.Integer => (object)token.ToObject<long>()!,
125+
JTokenType.Float => token.ToObject<double>()!,
126+
JTokenType.Boolean => token.ToObject<bool>()!,
127+
JTokenType.String => token.ToObject<string>()!,
128+
JTokenType.Null => null!,
129+
_ => throw new NotSupportedException(
130+
$"JsonValue converter only supports primitives. Got {token.Type}.")
131+
};
132+
133+
return primitive is null ? null : JsonValue.Create(primitive);
134+
}
135+
136+
public override void WriteJson(
137+
Newtonsoft.Json.JsonWriter writer,
138+
object? value,
139+
Newtonsoft.Json.JsonSerializer serializer)
140+
{
141+
if (value is null)
142+
{
143+
writer.WriteNull();
144+
return;
145+
}
146+
147+
var jv = (JsonValue)value;
148+
// Extract the underlying primitive and write it as a native JSON value
149+
if (jv.TryGetValue(out long l)) { writer.WriteValue(l); return; }
150+
if (jv.TryGetValue(out double d)) { writer.WriteValue(d); return; }
151+
if (jv.TryGetValue(out bool b)) { writer.WriteValue(b); return; }
152+
if (jv.TryGetValue(out string s)) { writer.WriteValue(s); return; }
153+
154+
// Fallback: write as string representation
155+
writer.WriteValue(jv.ToJsonString());
156+
}
157+
}
158+
}

0 commit comments

Comments
 (0)