Skip to content

feat(QueryPageOptions): SearchModel support serialize#7327

Merged
ArgoZhang merged 5 commits intomainfrom
refactor-seri
Dec 14, 2025
Merged

feat(QueryPageOptions): SearchModel support serialize#7327
ArgoZhang merged 5 commits intomainfrom
refactor-seri

Conversation

@ArgoZhang
Copy link
Copy Markdown
Member

@ArgoZhang ArgoZhang commented Dec 14, 2025

Link issues

fixes #7326

Summary By Copilot

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch

Summary by Sourcery

Add typed serialization support for QueryPageOptions.SearchModel and strengthen type handling in JSON converters.

New Features:

  • Persist and restore the concrete type of QueryPageOptions.SearchModel by including type metadata during JSON serialization.

Bug Fixes:

  • Handle missing or invalid type information safely when deserializing SearchModel and filter value types to avoid runtime failures.

Enhancements:

  • Introduce a TypeExtensions.GetSafeType helper and reuse it across JSON converters for safer type resolution.
  • Include assembly-qualified names when writing type information for filter value and search model serialization.

Tests:

  • Extend QueryPageOptions serialization tests to verify round-trip behavior and fallback handling for SearchModel, including ITableSearchModel implementations.

Copilot AI review requested due to automatic review settings December 14, 2025 03:05
@bb-auto bb-auto Bot added the enhancement New feature or request label Dec 14, 2025
@bb-auto bb-auto Bot added this to the v10.1.0 milestone Dec 14, 2025
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Dec 14, 2025

Reviewer's Guide

Implements typed, round‑trippable serialization for QueryPageOptions.SearchModel and hardens type resolution in JSON converters by emitting assembly‑qualified type names and using a safe type loader, with corresponding unit tests added.

Sequence diagram for QueryPageOptions.SearchModel JSON round-trip

sequenceDiagram
    participant Caller
    participant JsonSerializer
    participant JsonQueryPageOptionsConverter
    participant Utf8JsonWriter
    participant Utf8JsonReader
    participant TypeExtensions

    Caller->>JsonSerializer: Serialize(QueryPageOptions)
    JsonSerializer->>JsonQueryPageOptionsConverter: Write(writer, options)
    JsonQueryPageOptionsConverter->>Utf8JsonWriter: WriteStartObject
    alt SearchModel is not null
        JsonQueryPageOptionsConverter->>JsonQueryPageOptionsConverter: WriteSearchModel(writer, SearchModel, options)
        JsonQueryPageOptionsConverter->>Utf8JsonWriter: WriteStartObject(searchModel)
        JsonQueryPageOptionsConverter->>Utf8JsonWriter: WriteString(type, SearchModel.GetType().AssemblyQualifiedName)
        JsonQueryPageOptionsConverter->>Utf8JsonWriter: WritePropertyName(value)
        JsonQueryPageOptionsConverter->>Utf8JsonWriter: WriteRawValue(Serialize(SearchModel, options))
        JsonQueryPageOptionsConverter->>Utf8JsonWriter: WriteEndObject
    end
    JsonQueryPageOptionsConverter->>Utf8JsonWriter: WriteEndObject
    JsonSerializer-->>Caller: JsonPayload

    Caller->>JsonSerializer: Deserialize(JsonPayload, QueryPageOptions)
    JsonSerializer->>JsonQueryPageOptionsConverter: Read(ref reader, options)
    JsonQueryPageOptionsConverter->>Utf8JsonReader: Read searchModel property
    JsonQueryPageOptionsConverter->>JsonQueryPageOptionsConverter: ReadSearchModel(ref reader, options)
    JsonQueryPageOptionsConverter->>Utf8JsonReader: Read type
    JsonQueryPageOptionsConverter->>TypeExtensions: GetSafeType(typeName)
    TypeExtensions-->>JsonQueryPageOptionsConverter: ResolvedType or null
    alt ResolvedType is not null
        JsonQueryPageOptionsConverter->>JsonSerializer: Deserialize(ref reader, ResolvedType, options)
        JsonSerializer-->>JsonQueryPageOptionsConverter: StrongTypedInstance
        JsonQueryPageOptionsConverter->>JsonQueryPageOptionsConverter: Set SearchModel = StrongTypedInstance
    else ResolvedType is null
        JsonQueryPageOptionsConverter->>JsonSerializer: Deserialize<object>(ref reader, options)
        JsonSerializer-->>JsonQueryPageOptionsConverter: FallbackObject
        JsonQueryPageOptionsConverter->>JsonQueryPageOptionsConverter: Set SearchModel = FallbackObject
    end
    JsonQueryPageOptionsConverter-->>JsonSerializer: QueryPageOptions
    JsonSerializer-->>Caller: QueryPageOptions instance
Loading

Sequence diagram for FilterKeyValueAction fieldValueType handling

sequenceDiagram
    participant Caller
    participant JsonSerializer
    participant JsonFilterKeyValueActionConverter
    participant Utf8JsonWriter
    participant Utf8JsonReader
    participant TypeExtensions

    Caller->>JsonSerializer: Serialize(FilterKeyValueAction)
    JsonSerializer->>JsonFilterKeyValueActionConverter: Write(writer, options)
    JsonFilterKeyValueActionConverter->>Utf8JsonWriter: WriteStartObject
    JsonFilterKeyValueActionConverter->>Utf8JsonWriter: WriteString(fieldKey, value.FieldKey)
    JsonFilterKeyValueActionConverter->>JsonFilterKeyValueActionConverter: WriteFieldValueType(writer, value, options)
    alt FieldValue is not null
        JsonFilterKeyValueActionConverter->>Utf8JsonWriter: WriteString(fieldValueType, FieldValue.GetType().AssemblyQualifiedName)
    end
    JsonFilterKeyValueActionConverter->>Utf8JsonWriter: WritePropertyName(fieldValue)
    JsonFilterKeyValueActionConverter->>Utf8JsonWriter: WriteRawValue(Serialize(FieldValue, options))
    JsonFilterKeyValueActionConverter->>Utf8JsonWriter: WriteEndObject
    JsonSerializer-->>Caller: JsonPayload

    Caller->>JsonSerializer: Deserialize(JsonPayload, FilterKeyValueAction)
    JsonSerializer->>JsonFilterKeyValueActionConverter: Read(ref reader, options)
    JsonFilterKeyValueActionConverter->>Utf8JsonReader: Read fieldKey
    JsonFilterKeyValueActionConverter->>Utf8JsonReader: Read fieldValueType
    JsonFilterKeyValueActionConverter->>TypeExtensions: GetSafeType(typeName)
    TypeExtensions-->>JsonFilterKeyValueActionConverter: ResolvedType or null
    JsonFilterKeyValueActionConverter->>Utf8JsonReader: Read fieldValue
    alt ResolvedType is not null
        JsonFilterKeyValueActionConverter->>JsonSerializer: Deserialize(ref reader, ResolvedType, options)
        JsonSerializer-->>JsonFilterKeyValueActionConverter: StrongTypedFieldValue
    else ResolvedType is null
        JsonFilterKeyValueActionConverter->>JsonSerializer: Deserialize<object>(ref reader, options)
        JsonSerializer-->>JsonFilterKeyValueActionConverter: FallbackFieldValue
    end
    JsonFilterKeyValueActionConverter-->>JsonSerializer: FilterKeyValueAction instance
    JsonSerializer-->>Caller: FilterKeyValueAction
Loading

Class diagram for updated JSON converters and type resolution

classDiagram
    class QueryPageOptions {
        object SearchModel
        int PageIndex
        int PageItems
        bool IsPage
        bool IsPageable
        bool IsAdvanceSearch
        bool IsEnableSorting
        bool IsFilter
        bool IsEnd
        bool IsFirstPage
        bool IsLastPage
        bool IsMobile
        bool IsDialog
        bool IsVirtualScroll
        bool IsFixedHeader
        bool IsFixedFooter
        bool IsMultipleSelect
        bool IsTree
        bool IsExcel
        bool IsExport
        bool IsSearch
        bool IsAdvanceSearchDialog
        bool IsFixedColumns
        bool IsShowFooter
        bool IsShowTopToolbar
        bool IsShowExport
        bool IsShowColumnList
        bool IsShowSearch
        bool IsShowToolbar
        bool IsShowColumnVisible
        bool IsShowColumnExtend
        bool IsShowDefaultButtons
        bool IsShowPagination
        bool IsTreeLoaded
        bool IsToolbarTemplate
        bool IsShowAdd
        bool IsShowEdit
        bool IsShowDelete
        bool IsShowRefresh
        bool IsShowToggle
        bool IsShowLock
        bool IsShowDetail
        bool IsShowExtendButtons
        bool IsShowExportButtons
        bool IsShowPrint
        bool IsShowColumnReset
        bool IsShowExcel
        bool IsShowPdf
        bool IsShowCsv
        bool IsShowCopy
        bool IsShowAggregates
        bool IsShowEmpty
        bool IsShowCheckbox
        bool IsShowRadio
        bool IsShowLineNumber
        bool IsShowAdvanceSearch
        bool IsShowSearchText
        bool IsShowSearchButton
        bool IsShowColumn
        bool IsShowDefaultGroup
        bool IsShowTree
        bool IsShowColumnWidth
        bool IsShowExtendQuery
        bool IsShowToast
        bool IsShowColumnOptions
        bool IsShowLoading
        bool IsShowBooleanFilter
        bool IsStriped
        bool IsBordered
        bool IsHover
        bool IsShowToolbarTop
        bool IsShowToolbarBottom
        bool IsCardView
        bool IsFixedToolbar
        bool IsShowDetailRow
        bool IsShowColumnListCheckbox
        bool IsShowColumnListRadio
        bool IsResizable
        bool IsResizableColumn
        bool IsResizableRow
        bool IsResizableColumnWidth
        bool IsResizableRowHeight
        bool IsVirtualize
        bool IsFixedVirtualize
        bool IsFixedVirtualizeHeader
        bool IsFixedVirtualizeFooter
        bool IsFixedVirtualizeColumn
        bool IsFixedVirtualizeRow
        bool IsFixedVirtualizeColumnWidth
        bool IsFixedVirtualizeRowHeight
        bool IsFixedVirtualizeColumnTemplate
        bool IsFixedVirtualizeRowTemplate
        bool IsFixedVirtualizeDetailRow
        bool IsFixedVirtualizeDetailRowTemplate
        bool IsFixedVirtualizeToolbar
        bool IsFixedVirtualizeToolbarTemplate
        bool IsFixedVirtualizeFooterTemplate
        bool IsFixedVirtualizeHeaderTemplate
        bool IsFixedVirtualizeEmptyTemplate
        bool IsFixedVirtualizeLoadingTemplate
        bool IsFixedVirtualizeColumnOptions
        bool IsFixedVirtualizeColumnOptionsTemplate
        bool IsFixedVirtualizeColumnOptionsToolbar
        bool IsFixedVirtualizeColumnOptionsToolbarTemplate
        bool IsFixedVirtualizeColumnOptionsFooter
        bool IsFixedVirtualizeColumnOptionsFooterTemplate
        bool IsFixedVirtualizeColumnOptionsHeader
        bool IsFixedVirtualizeColumnOptionsHeaderTemplate
        bool IsFixedVirtualizeColumnOptionsEmptyTemplate
        bool IsFixedVirtualizeColumnOptionsLoadingTemplate
    }

    class JsonQueryPageOptionsConverter {
        +QueryPageOptions Read(Utf8JsonReader reader, JsonSerializerOptions options)
        +void Write(Utf8JsonWriter writer, QueryPageOptions value, JsonSerializerOptions options)
        -static void WriteSearchModel(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
        -static void ReadSearchModel(Utf8JsonReader reader, QueryPageOptions value, JsonSerializerOptions options)
    }

    class FilterKeyValueAction {
        string FieldKey
        object FieldValue
    }

    class JsonFilterKeyValueActionConverter {
        +FilterKeyValueAction Read(Utf8JsonReader reader, JsonSerializerOptions options)
        +void Write(Utf8JsonWriter writer, FilterKeyValueAction value, JsonSerializerOptions options)
        -static void WriteFieldValueType(Utf8JsonWriter writer, FilterKeyValueAction value, JsonSerializerOptions options)
    }

    class TypeExtensions {
        +string GetUniqueTypeName(Type type)
        +Type GetSafeType(string typeName)
    }

    class Utf8JsonWriter
    class Utf8JsonReader
    class JsonSerializerOptions

    JsonQueryPageOptionsConverter --> QueryPageOptions : converts
    JsonQueryPageOptionsConverter --> Utf8JsonWriter : writes
    JsonQueryPageOptionsConverter --> Utf8JsonReader : reads
    JsonQueryPageOptionsConverter --> TypeExtensions : uses GetSafeType

    JsonFilterKeyValueActionConverter --> FilterKeyValueAction : converts
    JsonFilterKeyValueActionConverter --> Utf8JsonWriter : writes
    JsonFilterKeyValueActionConverter --> Utf8JsonReader : reads
    JsonFilterKeyValueActionConverter --> TypeExtensions : uses GetSafeType

    TypeExtensions ..> Type : uses

    QueryPageOptions o-- object : SearchModel
    FilterKeyValueAction o-- object : FieldValue
Loading

File-Level Changes

Change Details Files
Add round‑trip JSON serialization/deserialization support for QueryPageOptions.SearchModel with type information.
  • Replace anonymous SearchModel in existing Serialize_Ok test with a concrete Foo type and assert properties after deserialization.
  • Add tests to verify SearchModel can be serialized/deserialized as a plain object, as an ITableSearchModel implementation, and when the stored type name cannot be resolved, falling back to JsonElement.
test/UnitTest/Extensions/QueryPageOptionsExtensionsTest.cs
Extend JsonQueryPageOptionsConverter to persist SearchModel with type metadata and read it back safely.
  • Refactor SearchModel read path to delegate to a new ReadSearchModel helper that reads a typed payload containing "type" and "value" fields.
  • Refactor SearchModel write path to delegate to a new WriteSearchModel helper that writes an object containing the SearchModel's assembly‑qualified type name and its serialized value.
  • On deserialization, resolve the type name via TypeExtensions.GetSafeType and fall back to object deserialization when the type cannot be found.
src/BootstrapBlazor/Converter/JsonQueryPageOptionConverter.cs
Introduce a reusable safe type resolution helper and apply it in JSON converters.
  • Add TypeExtensions.GetSafeType(string?) that wraps Type.GetType with null/empty checks and throwOnError: false.
  • Switch JsonFilterKeyValueActionConverter to use GetSafeType when reading fieldValueType from JSON.
  • Change FilterKeyValueAction serialization to output the assembly‑qualified name of FieldValue's type via a new WriteFieldValueType helper instead of just FullName.
src/BootstrapBlazor/Extensions/TypeExtensions.cs
src/BootstrapBlazor/Converter/JsonFilterKeyValueActionConverter.cs

Assessment against linked issues

Issue Objective Addressed Explanation
#7326 Implement proper serialization and deserialization support for QueryPageOptions.SearchModel so that its concrete type and value are preserved across JSON round-trips.

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

Copy link
Copy Markdown
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 - here's some feedback:

  • In both WriteSearchModel and the fieldValue write path, consider using JsonSerializer.Serialize(writer, value, options) instead of JsonSerializer.Serialize(...); writer.WriteRawValue(...) to avoid double serialization and rely on the serializer’s configured converters consistently.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In both `WriteSearchModel` and the `fieldValue` write path, consider using `JsonSerializer.Serialize(writer, value, options)` instead of `JsonSerializer.Serialize(...); writer.WriteRawValue(...)` to avoid double serialization and rely on the serializer’s configured converters consistently.

## Individual Comments

### Comment 1
<location> `src/BootstrapBlazor/Converter/JsonQueryPageOptionConverter.cs:301-298` </location>
<code_context>
         writer.WriteEndObject();
     }
+
+    private static void WriteSearchModel(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
+    {
+        writer.WriteStartObject("searchModel");
+        writer.WriteString("type", value.GetType().AssemblyQualifiedName);
+        writer.WritePropertyName("value");
+        writer.WriteRawValue(JsonSerializer.Serialize(value, options));
+        writer.WriteEndObject();
+    }
+
</code_context>

<issue_to_address>
**issue (bug_risk):** The new searchModel shape and embedded type information may break backward compatibility with existing JSON payloads.

Previously `searchModel` was serialized directly as its value (object/primitive/array). It’s now wrapped with type metadata:

```json
"searchModel": {
  "type": "...AssemblyQualifiedName...",
  "value": { ... }
}
```

`ReadSearchModel` assumes this new object shape; older payloads without the wrapper will be ignored (non-object token → no search model), causing silent data loss.

If compatibility with existing payloads is required, please either:
* Support both formats when reading (e.g., if `TokenType != StartObject`, fall back to `JsonSerializer.Deserialize<object>`), or
* Introduce a versioned field (e.g., `searchModelV2`) and keep the old behavior for `searchModel`.

Spelling this out will avoid subtle issues in mixed-version clients/servers.
</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.

@codecov
Copy link
Copy Markdown

codecov Bot commented Dec 14, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (258c546) to head (119b1b8).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #7327   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          747       747           
  Lines        32714     32760   +46     
  Branches      4534      4540    +6     
=========================================
+ Hits         32714     32760   +46     
Flag Coverage Δ
BB 100.00% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ArgoZhang ArgoZhang merged commit 894e27c into main Dec 14, 2025
9 of 10 checks passed
@ArgoZhang ArgoZhang deleted the refactor-seri branch December 14, 2025 03:09
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds serialization support for the SearchModel property in QueryPageOptions to enable proper round-trip serialization/deserialization. The implementation stores type information alongside the SearchModel value to preserve the concrete type during deserialization.

Key changes:

  • Implements type-aware serialization for SearchModel by storing AssemblyQualifiedName alongside the value
  • Refactors type deserialization to use a new shared GetSafeType utility method
  • Adds comprehensive unit tests for SearchModel serialization scenarios including anonymous types, ITableSearchModel implementations, and invalid type handling

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
test/UnitTest/Extensions/QueryPageOptionsExtensionsTest.cs Updated existing test to use concrete Foo type instead of anonymous object, added three new test methods for SearchModel serialization scenarios, and added FooSearchModel test helper class
src/BootstrapBlazor/Extensions/TypeExtensions.cs Added new GetSafeType utility method to safely retrieve Type instances from type names with null/error handling
src/BootstrapBlazor/Converter/JsonQueryPageOptionConverter.cs Refactored SearchModel serialization to include type metadata, extracted WriteSearchModel and ReadSearchModel helper methods
src/BootstrapBlazor/Converter/JsonFilterKeyValueActionConverter.cs Refactored to use new GetSafeType utility and extracted WriteFieldValueType helper method for consistency
src/BootstrapBlazor/BootstrapBlazor.csproj Version bumped to 10.1.4-beta07

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

? $"{type.FullName}-{type.TypeHandle.Value}"
: $"{type.FullName}";


Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

Extra blank line should be removed to follow consistent code formatting conventions within this file.

Suggested change

Copilot uses AI. Check for mistakes.
Comment on lines +200 to +202
var foo = expected.SearchModel as Foo;
Assert.NotNull(foo);
Assert.Equal("Test1", foo.Name);
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

The test verifies that the Name property is correctly deserialized, but it does not verify that the Count property (which is also set to 2 on line 185) is correctly deserialized. Consider adding an assertion like Assert.Equal(2, foo.Count); after line 202 to ensure complete test coverage of the SearchModel deserialization.

Copilot uses AI. Check for mistakes.
}
writer.WriteEndObject();
}

Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

The WriteSearchModel method lacks XML documentation comments. Consider adding a summary that describes the method's purpose and documents its parameters (writer, value, options) to maintain consistency with the public API documentation style in this codebase.

Suggested change
/// <summary>
/// Serializes the specified search model object into a JSON structure with type information.
/// </summary>
/// <param name="writer">The <see cref="Utf8JsonWriter"/> to write the JSON to.</param>
/// <param name="value">The search model object to serialize.</param>
/// <param name="options">The serializer options to use when serializing the object.</param>

Copilot uses AI. Check for mistakes.
if (value.FieldValue != null)
{
var type = value.FieldValue.GetType();
writer.WriteString("fieldValueType", type.AssemblyQualifiedName);
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

Using AssemblyQualifiedName for type serialization can potentially allow arbitrary type instantiation during deserialization if the JSON is received from untrusted sources. While the code uses GetSafeType with throwOnError: false, which prevents exceptions, it still allows any type that can be loaded to be instantiated. Consider implementing a whitelist of allowed types or adding validation to ensure only expected types can be deserialized through this mechanism.

Copilot uses AI. Check for mistakes.
}

[Fact]
public void SearchModel_Serialize_NullType()
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

The test method name "SearchModel_Serialize_NullType" is misleading. The test does not check null type handling but rather checks handling of an invalid/non-existent type name (by replacing "FooSearchModel" with "FooSearchModel1"). A more accurate name would be "SearchModel_Serialize_InvalidType" or "SearchModel_Serialize_UnknownType".

Suggested change
public void SearchModel_Serialize_NullType()
public void SearchModel_Serialize_InvalidType()

Copilot uses AI. Check for mistakes.
writer.WriteRawValue(JsonSerializer.Serialize(value, options));
writer.WriteEndObject();
}

Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

The ReadSearchModel method lacks XML documentation comments. Consider adding a summary that describes the method's purpose and documents its parameters (reader, value, options) to maintain consistency with the public API documentation style in this codebase.

Suggested change
/// <summary>
/// Reads and deserializes the <c>searchModel</c> property from the JSON input and assigns it to the <see cref="QueryPageOptions.SearchModel"/> property of the specified <paramref name="value"/> instance.
/// </summary>
/// <param name="reader">The <see cref="Utf8JsonReader"/> to read from. Passed by reference.</param>
/// <param name="value">The <see cref="QueryPageOptions"/> instance to assign the deserialized search model to.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use for deserialization.</param>

Copilot uses AI. Check for mistakes.

writer.WriteEndObject();
}

Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

The WriteFieldValueType method lacks XML documentation comments. Consider adding a summary that describes the method's purpose and documents its parameters (writer, value, options) to maintain consistency with the public API documentation style in this codebase.

Suggested change
/// <summary>
/// Writes the type information of the <c>FieldValue</c> property to the JSON output if it is not null.
/// </summary>
/// <param name="writer">The <see cref="Utf8JsonWriter"/> to write to.</param>
/// <param name="value">The <see cref="FilterKeyValueAction"/> instance containing the field value.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use for serialization.</param>

Copilot uses AI. Check for mistakes.
private static void WriteSearchModel(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
{
writer.WriteStartObject("searchModel");
writer.WriteString("type", value.GetType().AssemblyQualifiedName);
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

Using AssemblyQualifiedName for type serialization can potentially allow arbitrary type instantiation during deserialization if the JSON is received from untrusted sources. While the code uses GetSafeType with throwOnError: false, which prevents exceptions, it still allows any type that can be loaded to be instantiated. Consider implementing a whitelist of allowed types or adding validation to ensure only expected types can be deserialized through this mechanism.

Copilot uses AI. Check for mistakes.
Comment on lines +329 to +342
reader.Read();
propertyName = reader.GetString();
if (propertyName == "value")
{
reader.Read();
if (type != null)
{
value.SearchModel = JsonSerializer.Deserialize(ref reader, type, options);
}
else
{
value.SearchModel = JsonSerializer.Deserialize<object>(ref reader, options);
}
}
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

The deserialization logic assumes the JSON properties "type" and "value" appear in a specific order, but JSON object property order is not guaranteed by the specification. If the "value" property appears before "type" in the JSON, the type variable will be null and deserialization will fall back to using JsonElement. Consider reading both properties first into local variables before attempting deserialization, or handle the case where properties might appear in any order.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(QueryPageOptions): SearchModel support serialize

2 participants