Skip to content

CSHARP-5825: Support (de)serialization between BSON and EJSON#1939

Open
papafe wants to merge 7 commits intomongodb:mainfrom
papafe:csharp5825
Open

CSHARP-5825: Support (de)serialization between BSON and EJSON#1939
papafe wants to merge 7 commits intomongodb:mainfrom
papafe:csharp5825

Conversation

@papafe
Copy link
Copy Markdown
Contributor

@papafe papafe commented Apr 7, 2026

No description provided.

@papafe papafe requested review from Copilot and removed request for Copilot April 7, 2026 08:39
@papafe papafe requested a review from a team as a code owner April 7, 2026 08:39
@papafe papafe requested a review from adelinowona April 7, 2026 08:39
@papafe papafe added the feature Adds new user-facing functionality. label Apr 7, 2026
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The new methods in this file break the alphabetical ordering.

Comment on lines 43 to 44
DeserializeEJsonExpression,
DateAddExpression,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: DateAddExpression should come before DeserializeEJsonExpression.

Comment on lines 31 to 32
private static readonly MethodInfo __deserializeEJson;
private static readonly MethodInfo __dateFromStringWithFormat;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

__dateFromStringWithFormat (and the other __dateFromString* variants) should all appear before __deserializeEJson .

Same issue in the public properties section (lines 113-117): DeserializeEJson is wedged between DateFromString and DateFromStringWithFormat*.

private bool? _relaxed;

/// <summary>
/// The relaxed parameter. When true, produces relaxed Extended JSON format. When false, produces canonical format. Defaults to true.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The sentence "Defaults to true" could be misread as "this C# property defaults to true." The property defaults to null (not set). The server defaults to true when the parameter is omitted. I would suggest clarifying: "The server defaults to true when this is not specified."

@papafe papafe requested review from adelinowona and Copilot April 13, 2026 13:21
@papafe
Copy link
Copy Markdown
Contributor Author

papafe commented Apr 13, 2026

@adelinowona I agree with all your comments, fixed.

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

Adds LINQ3/MQL support for Extended JSON v2 (EJSON) round-tripping by introducing $serializeEJSON and $deserializeEJSON operators, including public Mql APIs, AST nodes, translators, and both unit + integration tests.

Changes:

  • Introduce Mql.SerializeEJson / Mql.DeserializeEJson APIs and corresponding options types.
  • Implement LINQ3 translation + AST rendering for $serializeEJSON and $deserializeEJSON.
  • Add translator unit tests and server-gated integration tests for both operators.

Reviewed changes

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

Show a summary per file
File Description
tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/SerializeEJsonMethodToAggregationExpressionTranslatorTests.cs Adds unit tests asserting correct AST for $serializeEJSON translation/options.
tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/DeserializeEJsonMethodToAggregationExpressionTranslatorTests.cs Adds unit tests asserting correct AST for $deserializeEJSON translation/options.
tests/MongoDB.Driver.Tests/Linq/Integration/MqlSerializeEJsonTests.cs Adds integration tests validating server execution/behavior of $serializeEJSON.
tests/MongoDB.Driver.Tests/Linq/Integration/MqlDeserializeEJsonTests.cs Adds integration tests validating server execution/behavior of $deserializeEJSON.
src/MongoDB.Driver/SerializeEJsonOptions.cs Introduces public options types for serialization (Relaxed, OnError).
src/MongoDB.Driver/DeserializeEJsonOptions.cs Introduces public options types for deserialization (OnError).
src/MongoDB.Driver/Mql.cs Adds new public Mql entry points for (de)serializing EJSON.
src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/SerializeEJsonMethodToAggregationExpressionTranslator.cs Implements LINQ3 translation into $serializeEJSON AST.
src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/DeserializeEJsonMethodToAggregationExpressionTranslator.cs Implements LINQ3 translation into $deserializeEJSON AST.
src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodCallExpressionToAggregationExpressionTranslator.cs Wires new Mql methods into the method-call translator dispatch.
src/MongoDB.Driver/Linq/Linq3Implementation/SerializerFinders/SerializerFinderVisitMethodCall.cs Deduces serializers for the new Mql method calls.
src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/MqlMethod.cs Adds reflection handles for the new Mql methods.
src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Visitors/AstNodeVisitor.cs Adds visitor support for the new AST node kinds.
src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Expressions/AstSerializeEJsonExpression.cs New AST node + renderer for $serializeEJSON.
src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Expressions/AstDeserializeEJsonExpression.cs New AST node + renderer for $deserializeEJSON.
src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Expressions/AstExpression.cs Adds factory helpers to create the new AST expression nodes.
src/MongoDB.Driver/Linq/Linq3Implementation/Ast/AstNodeType.cs Adds new AST node type enum entries.
src/MongoDB.Driver/Core/Misc/Feature.cs Adds feature flags for gating tests/operator availability by server wire version.

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

Comment on lines +113 to +114
var relaxedValue = relaxedExpression.GetConstantValue<bool>(expression);
relaxedAst = AstExpression.Constant(relaxedValue);
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

In the MemberInit options translation for Relaxed, the code calls GetConstantValue<bool>(...), but SerializeEJsonOptions.Relaxed is bool?. If a user writes new SerializeEJsonOptions<T> { Relaxed = null } (or an expression that evaluates to null), this will fail during translation instead of treating it as “not specified”. Consider reading a bool? constant and only emitting relaxed when it has a value (matching the constant-options path which checks HasValue).

Suggested change
var relaxedValue = relaxedExpression.GetConstantValue<bool>(expression);
relaxedAst = AstExpression.Constant(relaxedValue);
var relaxedValue = relaxedExpression.GetConstantValue<bool?>(expression);
if (relaxedValue.HasValue)
{
relaxedAst = AstExpression.Constant(relaxedValue.Value);
}

Copilot uses AI. Check for mistakes.
Comment thread src/MongoDB.Driver/SerializeEJsonOptions.cs Outdated

/// <summary>
/// Represents the options parameter for <see cref="Mql.DeserializeEJson{TInput, TOutput}(TInput, DeserializeEJsonOptions{TOutput})"/>.
/// This class allows to set 'onError'.
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

The XML doc comment has a grammatical issue: “This class allows to set 'onError'.” should be “This class allows setting 'onError'.” (or similar).

Suggested change
/// This class allows to set 'onError'.
/// This class allows setting 'onError'.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +14
/* Copyright 2010-present MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

The Apache license header formatting here is inconsistent with other files in this directory (e.g., AstDateFromStringExpression.cs uses *-prefixed lines). Consider aligning the comment formatting to the standard header style used elsewhere to keep license headers uniform.

Suggested change
/* Copyright 2010-present MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright 2010-present MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This seems to be incorrect.

Comment on lines +1 to +14
/* Copyright 2010-present MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

The Apache license header formatting here is inconsistent with other files in this directory (e.g., AstDateFromStringExpression.cs uses *-prefixed lines). Consider aligning the comment formatting to the standard header style used elsewhere to keep license headers uniform.

Suggested change
/* Copyright 2010-present MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright 2010-present MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

Copilot uses AI. Check for mistakes.
using MongoDB.Driver.Core.Misc;
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Visitors;

namespace MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Minor: Could be file-scoped namespace.


public override AstNode Accept(AstNodeVisitor visitor)
{
return visitor.VisitDeserializeEJsonExpression(this);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Minor: Could be expression-body method.

if (IsNotKnown(node))
{
var outputType = method.GetGenericArguments()[1];
var outputSerializer = BsonSerializer.LookupSerializer(outputType);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think in this case we should not lookup for serializer, as it might resolve a wrong one. We should assign the serializer from the other part of the expression. For example, if we have code like this:

queryable.Select(d => new MyModel(){ Property = Mql.DeserializeEJson(d.StringField)})

We should deduce the serializer from serializer for Property. It might be already handled by VisitMemberInitExpression. Could you please double-check if the current implementation will pick up a custom serializer, if parent document serializer implements IBsonDocumentSerializer?

if (IsNotKnown(node))
{
var outputType = method.GetGenericArguments()[1];
var outputSerializer = BsonSerializer.LookupSerializer(outputType);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same as above.

Comment thread src/MongoDB.Driver/Mql.cs
/// <param name="value">The value to deserialize.</param>
/// <param name="options">The deserialization options.</param>
/// <returns>The deserialized value.</returns>
public static TOutput DeserializeEJson<TInput, TOutput>(TInput value, DeserializeEJsonOptions<TOutput> options = null)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not sure if I dig deep enough, but does runtime type should be the same? Is there any use case when TInput != TOutput?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, it could be for instance <BsonDocument, BsonValue>, <BsonValue, string>

@sanych-sun
Copy link
Copy Markdown
Member

Also please add unit tests for serializerFinder. See MongoDB.Driver.Tests.Linq.Linq3Implementation.SerializerFinders.MqlTests.

translation.Ast.Render().Should().Be(BsonDocument.Parse(expectedAst));
}

public static IEnumerable<object[]> SupportedTestCases =
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit (test coverage): The unit tests cover the MemberInitExpression path (inline new ... { OnError = "fallback" }) and the ConstantExpression path with null. The ConstantExpression path
with a non-null options object (i.e., options created outside the lambda and captured as a closure) isn't directly tested. That path is straightforward — it just reads property values off the object — so this
is minor. Worth noting in case you want to add a case for completeness.

Same for the SerializeEJson translator tests

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Adds new user-facing functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants