Skip to content

Commit 81ad6a1

Browse files
bulbfreemanjyemin
authored andcommitted
Support decoding nulls for non-primitive fields in Java records (#1223)
JAVA-5203
1 parent 0fb1bb4 commit 81ad6a1

File tree

3 files changed

+63
-0
lines changed

3 files changed

+63
-0
lines changed

Diff for: bson-record-codec/src/main/org/bson/codecs/record/RecordCodec.java

+8
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.bson.codecs.record;
1818

19+
import org.bson.BsonInvalidOperationException;
1920
import org.bson.BsonReader;
2021
import org.bson.BsonType;
2122
import org.bson.BsonWriter;
@@ -62,6 +63,7 @@ private static final class ComponentModel {
6263
private final Codec<?> codec;
6364
private final int index;
6465
private final String fieldName;
66+
private final boolean isNullable;
6567

6668
private ComponentModel(final List<Type> typeParameters, final RecordComponent component, final CodecRegistry codecRegistry,
6769
final int index) {
@@ -70,6 +72,7 @@ private ComponentModel(final List<Type> typeParameters, final RecordComponent co
7072
this.codec = computeCodec(typeParameters, component, codecRegistry);
7173
this.index = index;
7274
this.fieldName = computeFieldName(component);
75+
this.isNullable = !component.getType().isPrimitive();
7376
}
7477

7578
String getComponentName() {
@@ -275,6 +278,11 @@ public T decode(final BsonReader reader, final DecoderContext decoderContext) {
275278
if (LOGGER.isTraceEnabled()) {
276279
LOGGER.trace(format("Found property not present in the ClassModel: %s", fieldName));
277280
}
281+
} else if (reader.getCurrentBsonType() == BsonType.NULL) {
282+
if (!componentModel.isNullable) {
283+
throw new BsonInvalidOperationException(format("Null value on primitive field: %s", componentModel.fieldName));
284+
}
285+
reader.readNull();
278286
} else {
279287
constructorArguments[componentModel.index] = decoderContext.decodeWithChildContext(componentModel.codec, reader);
280288
}

Diff for: bson-record-codec/src/test/unit/org/bson/codecs/record/RecordCodecTest.java

+32
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import org.bson.BsonDocumentWriter;
2323
import org.bson.BsonDouble;
2424
import org.bson.BsonInt32;
25+
import org.bson.BsonInvalidOperationException;
26+
import org.bson.BsonNull;
2527
import org.bson.BsonObjectId;
2628
import org.bson.BsonString;
2729
import org.bson.codecs.DecoderContext;
@@ -49,6 +51,7 @@
4951
import org.bson.codecs.record.samples.TestRecordWithMapOfRecords;
5052
import org.bson.codecs.record.samples.TestRecordWithNestedParameterized;
5153
import org.bson.codecs.record.samples.TestRecordWithNestedParameterizedRecord;
54+
import org.bson.codecs.record.samples.TestRecordWithNullableField;
5255
import org.bson.codecs.record.samples.TestRecordWithParameterizedRecord;
5356
import org.bson.codecs.record.samples.TestRecordWithPojoAnnotations;
5457
import org.bson.codecs.record.samples.TestSelfReferentialHolderRecord;
@@ -325,6 +328,35 @@ public void testRecordWithNulls() {
325328
assertEquals(testRecord, decoded);
326329
}
327330

331+
@Test
332+
public void testRecordWithStoredNulls() {
333+
var codec = createRecordCodec(TestRecordWithNullableField.class, Bson.DEFAULT_CODEC_REGISTRY);
334+
var identifier = new ObjectId();
335+
var testRecord = new TestRecordWithNullableField(identifier, null, 42);
336+
337+
var document = new BsonDocument("_id", new BsonObjectId(identifier))
338+
.append("name", new BsonNull())
339+
.append("age", new BsonInt32(42));
340+
341+
// when
342+
var decoded = codec.decode(new BsonDocumentReader(document), DecoderContext.builder().build());
343+
344+
// then
345+
assertEquals(testRecord, decoded);
346+
}
347+
348+
@Test
349+
public void testExceptionsWithStoredNullsOnPrimitiveField() {
350+
var codec = createRecordCodec(TestRecordWithNullableField.class, Bson.DEFAULT_CODEC_REGISTRY);
351+
352+
var document = new BsonDocument("_id", new BsonObjectId(new ObjectId()))
353+
.append("name", new BsonString("Felix"))
354+
.append("age", new BsonNull());
355+
356+
assertThrows(BsonInvalidOperationException.class, () ->
357+
codec.decode(new BsonDocumentReader(document), DecoderContext.builder().build()));
358+
}
359+
328360
@Test
329361
public void testRecordWithExtraData() {
330362
var codec = createRecordCodec(TestRecordWithDeprecatedAnnotations.class, Bson.DEFAULT_CODEC_REGISTRY);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.bson.codecs.record.samples;
18+
19+
import org.bson.codecs.pojo.annotations.BsonId;
20+
import org.bson.types.ObjectId;
21+
22+
public record TestRecordWithNullableField(@BsonId ObjectId id, String name, int age) {
23+
}

0 commit comments

Comments
 (0)