Skip to content

Conversation

@kimdiego2098
Copy link
Collaborator

@kimdiego2098 kimdiego2098 commented Dec 12, 2025

Summary by Sourcery

Fix JSON serialization and deserialization for QueryPageOptions filter data by introducing a reusable typed-object converter and consolidating filter-related fields into FilterKeyValueAction.

Bug Fixes:

  • Correct QueryPageOptions JSON converter to serialize and deserialize filter criteria via a single FilterKeyValueAction field instead of multiple search/filter collections.

Enhancements:

  • Introduce an ObjectWithTypeConverter to preserve runtime type information when serializing polymorphic object properties such as SearchModel and filter field values.
  • Adjust QueryPageOptions and FilterKeyValueAction to leverage the new typed-object converter and cache FilterKeyValueAction instances during filter generation.
  • Add a sample table query demonstrating round-trip JSON serialization/deserialization of QueryPageOptions filters for validation.
@bb-auto
Copy link

bb-auto bot commented Dec 12, 2025

Thanks for your PR, @kimdiego2098. Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 12, 2025

Reviewer's Guide

Refactors QueryPageOptions JSON (de)serialization to use a single FilterKeyValueAction representation and a polymorphic object converter, ensuring filters and search models serialize/deserialize correctly and adds a small sample test usage.

Sequence diagram for QueryPageOptions round-trip JSON serialization with FilterKeyValueAction

sequenceDiagram actor SampleComponent participant JsonSerializer as SystemTextJson_JsonSerializer participant Converter as JsonQueryPageOptionsConverter participant Options as QueryPageOptions participant Ext as QueryPageOptionsExtensions participant Fkva as FilterKeyValueAction SampleComponent->>Options: Configure filters and searches SampleComponent->>JsonSerializer: Serialize(Options) JsonSerializer->>Converter: Write(writer, Options, options) Converter->>Options: Check Searches, CustomerSearches, AdvanceSearches, Filters alt Any_filter_list_non_empty Converter->>Ext: ToFilter(Options) alt Existing_FilterKeyValueAction Ext-->>Converter: return Options.FilterKeyValueAction else Build_new_FilterKeyValueAction Ext->>Fkva: new FilterKeyValueAction() Ext->>Fkva: Populate from Searches and Filters Ext-->>Converter: return Fkva end Converter->>JsonSerializer: Write filterKeyValueAction property end Converter-->>JsonSerializer: Finish writing QueryPageOptions JSON JsonSerializer-->>SampleComponent: JSON string SampleComponent->>JsonSerializer: Deserialize(json, QueryPageOptions) JsonSerializer->>Converter: Read(ref reader, typeToConvert, options) Converter->>Options: Create new QueryPageOptions Converter->>Options: Set IsVirtualScroll, IsFirstQuery, etc. Converter->>Options: Read filterKeyValueAction and assign FilterKeyValueAction Converter-->>JsonSerializer: return Options JsonSerializer-->>SampleComponent: QueryPageOptions instance with FilterKeyValueAction SampleComponent->>Ext: ToFilter(Options) Ext-->>SampleComponent: returns existing FilterKeyValueAction from Options.FilterKeyValueAction 
Loading

Class diagram for updated QueryPageOptions JSON serialization model

classDiagram class QueryPageOptions { object SearchModel List~IFilterAction~ Searches List~IFilterAction~ CustomerSearches List~IFilterAction~ AdvanceSearches List~IFilterAction~ Filters internal FilterKeyValueAction FilterKeyValueAction bool IsVirtualScroll bool IsFirstQuery bool IsAdvanceSearch bool IsSimpleSearch string? SearchText int PageIndex int PageItems } class FilterKeyValueAction { string? FieldName object? FieldValue string? Operator bool IsAnd bool IsOr } class ObjectWithTypeConverter { +object Read(Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) +void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) } class JsonQueryPageOptionsConverter { +QueryPageOptions Read(Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) +void Write(Utf8JsonWriter writer, QueryPageOptions value, JsonSerializerOptions options) } class QueryPageOptionsExtensions { +FilterKeyValueAction ToFilter(QueryPageOptions option) } interface IFilterAction QueryPageOptions --> "*" IFilterAction : uses QueryPageOptions --> FilterKeyValueAction : aggregates QueryPageOptionsExtensions ..> QueryPageOptions : extension QueryPageOptionsExtensions ..> FilterKeyValueAction : creates JsonQueryPageOptionsConverter ..> QueryPageOptions : converts JsonQueryPageOptionsConverter ..> FilterKeyValueAction : serializes ObjectWithTypeConverter <.. QueryPageOptions : applied_to_SearchModel ObjectWithTypeConverter <.. FilterKeyValueAction : applied_to_FieldValue 
Loading

File-Level Changes

Change Details Files
Unify filter-related JSON representation on QueryPageOptions to a single FilterKeyValueAction property and update its converter.
  • Replace deserialization of searches/customerSearches/advanceSearches/filters arrays with a single filterKeyValueAction object deserialization that populates QueryPageOptions.FilterKeyValueAction.
  • Modify serialization logic to emit a filterKeyValueAction property derived from the existing filter/search collections using QueryPageOptions.ToFilter().
  • Introduce an internal FilterKeyValueAction property on QueryPageOptions that is ignored by default JSON serialization but used internally to cache deserialized filters and shortcut ToFilter().
src/BootstrapBlazor/Converter/JsonQueryPageOptionConverter.cs
src/BootstrapBlazor/Options/QueryPageOptions.cs
src/BootstrapBlazor/Extensions/QueryPageOptionsExtensions.cs
Introduce a polymorphic JSON converter for object values and apply it to SearchModel and FilterKeyValueAction.FieldValue to preserve runtime type information.
  • Add ObjectWithTypeConverter implementing JsonConverter that wraps values as {"$type": ..., "value": ...} and reconstructs them on read using Type.GetType.
  • Annotate QueryPageOptions.SearchModel and FilterKeyValueAction.FieldValue with ObjectWithTypeConverter so they serialize/deserialize with type metadata instead of being treated as plain object.
src/BootstrapBlazor/Converter/ObjectWithTypeConverter.cs
src/BootstrapBlazor/Options/QueryPageOptions.cs
src/BootstrapBlazor/Components/Filters/FilterKeyValueAction.cs
Hide internal filter/search collections from JSON and add a sample to validate serialization round-trip of QueryPageOptions in a table scenario.
  • Mark Searches, CustomerSearches, AdvanceSearches, Filters, and FilterKeyValueAction on QueryPageOptions with JsonIgnore to avoid duplicating filter state in the JSON payload.
  • Update sample TablesFilter.razor.cs to serialize and then deserialize QueryPageOptions inside OnQueryAsync as a sanity check for the new serialization behavior.
src/BootstrapBlazor/Options/QueryPageOptions.cs
src/BootstrapBlazor.Server/Components/Samples/Table/TablesFilter.razor.cs

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@bb-auto bb-auto bot requested a review from ArgoZhang December 12, 2025 09:05
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and found some issues that need to be addressed.

  • The new ObjectWithTypeConverter deserializes arbitrary types via Type.GetType and assembly-qualified names, which is risky and brittle; consider restricting allowed types/namespaces and handling Type.GetType returning null or invalid payloads more defensively.
  • When no $type is present, ObjectWithTypeConverter returns a JsonElement clone, which changes the runtime type compared to previously using a concrete model; ensure this is compatible with existing usages of SearchModel/FieldValue or consider a fallback strategy that preserves expected types.
  • The JSON round-trip in TablesFilter.OnQueryAsync appears to be test/debug-only and adds overhead on each query; consider removing it or wrapping it in a DEBUG conditional.
Prompt for AI Agents
Please address the comments from this code review: ## Overall Comments - The new ObjectWithTypeConverter deserializes arbitrary types via Type.GetType and assembly-qualified names, which is risky and brittle; consider restricting allowed types/namespaces and handling Type.GetType returning null or invalid payloads more defensively. - When no $type is present, ObjectWithTypeConverter returns a JsonElement clone, which changes the runtime type compared to previously using a concrete model; ensure this is compatible with existing usages of SearchModel/FieldValue or consider a fallback strategy that preserves expected types. - The JSON round-trip in TablesFilter.OnQueryAsync appears to be test/debug-only and adds overhead on each query; consider removing it or wrapping it in a DEBUG conditional. ## Individual Comments ### Comment 1 <location> `src/BootstrapBlazor/Converter/JsonQueryPageOptionConverter.cs:190` </location> <code_context> -  } -  if (value.Filters.Count != 0) + +  if (value.Searches.Count != 0||value.CustomerSearches.Count != 0||value.AdvanceSearches.Count != 0|| value.Filters.Count != 0)  { -  writer.WriteStartArray("filters"); </code_context> <issue_to_address> **issue (bug_risk):** Guard against the case where `FilterKeyValueAction` is already set but filter lists are empty. Because deserialization sets `FilterKeyValueAction` directly and leaves `Searches` / `CustomerSearches` / `AdvanceSearches` / `Filters` empty, this `if` will skip writing filters when all lists are empty even if `FilterKeyValueAction` is populated (e.g., after a round-trip). To avoid losing existing filter state on re‑serialization, also include `value.FilterKeyValueAction != null` in this condition. </issue_to_address> ### Comment 2 <location> `src/BootstrapBlazor/Components/Filters/FilterKeyValueAction.cs:23` </location> <code_context> /// <summary> /// 获得/设置 Filter 项字段值 /// </summary> + [JsonConverter(typeof(ObjectWithTypeConverter))] public object? FieldValue { get; set; } </code_context> <issue_to_address> **🚨 issue (security):** Using type information from JSON for polymorphic deserialization can be a security risk. Annotating `FieldValue` with `ObjectWithTypeConverter` allows clients to control the `$type` used during deserialization. Because `Type.GetType` with assembly-qualified names can resolve arbitrary types, this is unsafe for untrusted input. Use a restricted type resolution strategy (e.g., a whitelist or custom resolver) instead of calling `Type.GetType` directly on user data. </issue_to_address> ### Comment 3 <location> `src/BootstrapBlazor/Converter/ObjectWithTypeConverter.cs:20-23` </location> <code_context> +  if (!doc.RootElement.TryGetProperty("$type", out var typeProp)) +  return doc.RootElement.Clone(); // 无类型信息 + +  var type = Type.GetType(typeProp.GetString()!)!; + +  var valueElement = doc.RootElement.GetProperty("value"); +  return JsonSerializer.Deserialize(valueElement.GetRawText(), type, options); + } + </code_context> <issue_to_address> **issue:** Handle cases where the specified type cannot be resolved or the `value` property is missing. `Type.GetType` can return null (e.g., type moved, different assembly, trimming), and the null-forgiving operator will hide this until a later NRE. Also, if `$type` is present but `value` is missing, `GetProperty("value")` will throw. Please handle these cases explicitly (e.g., detect and throw a `JsonException`, fall back to `JsonElement`, or apply a migration strategy) instead of relying on implicit null/KeyNotFound exceptions. </issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
}
if (value.Filters.Count != 0)

if (value.Searches.Count != 0||value.CustomerSearches.Count != 0||value.AdvanceSearches.Count != 0|| value.Filters.Count != 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Guard against the case where FilterKeyValueAction is already set but filter lists are empty.

Because deserialization sets FilterKeyValueAction directly and leaves Searches / CustomerSearches / AdvanceSearches / Filters empty, this if will skip writing filters when all lists are empty even if FilterKeyValueAction is populated (e.g., after a round-trip). To avoid losing existing filter state on re‑serialization, also include value.FilterKeyValueAction != null in this condition.

/// <summary>
/// 获得/设置 Filter 项字段值
/// </summary>
[JsonConverter(typeof(ObjectWithTypeConverter))]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 issue (security): Using type information from JSON for polymorphic deserialization can be a security risk.

Annotating FieldValue with ObjectWithTypeConverter allows clients to control the $type used during deserialization. Because Type.GetType with assembly-qualified names can resolve arbitrary types, this is unsafe for untrusted input. Use a restricted type resolution strategy (e.g., a whitelist or custom resolver) instead of calling Type.GetType directly on user data.

Comment on lines +20 to +23
var type = Type.GetType(typeProp.GetString()!)!;

var valueElement = doc.RootElement.GetProperty("value");
return JsonSerializer.Deserialize(valueElement.GetRawText(), type, options);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Handle cases where the specified type cannot be resolved or the value property is missing.

Type.GetType can return null (e.g., type moved, different assembly, trimming), and the null-forgiving operator will hide this until a later NRE. Also, if $type is present but value is missing, GetProperty("value") will throw. Please handle these cases explicitly (e.g., detect and throw a JsonException, fall back to JsonElement, or apply a migration strategy) instead of relying on implicit null/KeyNotFound exceptions.

ArgoZhang
ArgoZhang previously approved these changes Dec 12, 2025
ArgoZhang and others added 2 commits December 12, 2025 19:57
Co-Authored-By: Diego <82756760+kimdiego2098@users.noreply.github.com>
@ArgoZhang ArgoZhang changed the title fix(Table): 修复QueryPageOptions json序列化 fix(Table): support QueryPageOptions serialization Dec 12, 2025
@ArgoZhang ArgoZhang merged commit ff28f9c into main Dec 12, 2025
3 of 4 checks passed
@ArgoZhang ArgoZhang deleted the QueryPageOptions_json branch December 12, 2025 12:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants