CSHARP-5825: Support (de)serialization between BSON and EJSON#1939
CSHARP-5825: Support (de)serialization between BSON and EJSON#1939papafe wants to merge 7 commits intomongodb:mainfrom
Conversation
There was a problem hiding this comment.
The new methods in this file break the alphabetical ordering.
| DeserializeEJsonExpression, | ||
| DateAddExpression, |
There was a problem hiding this comment.
nit: DateAddExpression should come before DeserializeEJsonExpression.
| private static readonly MethodInfo __deserializeEJson; | ||
| private static readonly MethodInfo __dateFromStringWithFormat; |
There was a problem hiding this comment.
__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. |
There was a problem hiding this comment.
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."
|
@adelinowona I agree with all your comments, fixed. |
There was a problem hiding this comment.
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.DeserializeEJsonAPIs and corresponding options types. - Implement LINQ3 translation + AST rendering for
$serializeEJSONand$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.
| var relaxedValue = relaxedExpression.GetConstantValue<bool>(expression); | ||
| relaxedAst = AstExpression.Constant(relaxedValue); |
There was a problem hiding this comment.
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).
| var relaxedValue = relaxedExpression.GetConstantValue<bool>(expression); | |
| relaxedAst = AstExpression.Constant(relaxedValue); | |
| var relaxedValue = relaxedExpression.GetConstantValue<bool?>(expression); | |
| if (relaxedValue.HasValue) | |
| { | |
| relaxedAst = AstExpression.Constant(relaxedValue.Value); | |
| } |
|
|
||
| /// <summary> | ||
| /// Represents the options parameter for <see cref="Mql.DeserializeEJson{TInput, TOutput}(TInput, DeserializeEJsonOptions{TOutput})"/>. | ||
| /// This class allows to set 'onError'. |
There was a problem hiding this comment.
The XML doc comment has a grammatical issue: “This class allows to set 'onError'.” should be “This class allows setting 'onError'.” (or similar).
| /// This class allows to set 'onError'. | |
| /// This class allows setting 'onError'. |
| /* 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. | ||
| */ |
There was a problem hiding this comment.
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.
| /* 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. | |
| */ |
There was a problem hiding this comment.
This seems to be incorrect.
| /* 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. | ||
| */ |
There was a problem hiding this comment.
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.
| /* 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. | |
| */ |
| using MongoDB.Driver.Core.Misc; | ||
| using MongoDB.Driver.Linq.Linq3Implementation.Ast.Visitors; | ||
|
|
||
| namespace MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions |
There was a problem hiding this comment.
Minor: Could be file-scoped namespace.
|
|
||
| public override AstNode Accept(AstNodeVisitor visitor) | ||
| { | ||
| return visitor.VisitDeserializeEJsonExpression(this); |
There was a problem hiding this comment.
Minor: Could be expression-body method.
| if (IsNotKnown(node)) | ||
| { | ||
| var outputType = method.GetGenericArguments()[1]; | ||
| var outputSerializer = BsonSerializer.LookupSerializer(outputType); |
There was a problem hiding this comment.
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); |
| /// <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) |
There was a problem hiding this comment.
Not sure if I dig deep enough, but does runtime type should be the same? Is there any use case when TInput != TOutput?
There was a problem hiding this comment.
Yes, it could be for instance <BsonDocument, BsonValue>, <BsonValue, string>
|
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 = |
There was a problem hiding this comment.
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
No description provided.