Skip to content

Commit d8a66bc

Browse files
committed
Enable IJSObjectReference to handle null/undefined values
1 parent cb4eb4a commit d8a66bc

File tree

4 files changed

+63
-17
lines changed

4 files changed

+63
-17
lines changed

src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,12 @@ export module DotNet {
155155
* @throws Error if the given value is not an Object.
156156
*/
157157
export function createJSObjectReference(jsObject: any): any {
158+
if (jsObject === null || jsObject === undefined) {
159+
return {
160+
[jsObjectIdKey]: -1
161+
};
162+
}
163+
158164
if (jsObject && (typeof jsObject === "object" || jsObject instanceof Function)) {
159165
cachedJSObjectsById[nextJsObjectId] = new JSObject(jsObject);
160166

@@ -220,7 +226,7 @@ export module DotNet {
220226
export function disposeJSObjectReference(jsObjectReference: any): void {
221227
const id = jsObjectReference && jsObjectReference[jsObjectIdKey];
222228

223-
if (typeof id === "number") {
229+
if (typeof id === "number" && id !== -1) {
224230
disposeJSObjectReferenceById(id);
225231
}
226232
}
@@ -573,7 +579,7 @@ export module DotNet {
573579
}
574580

575581
/** Traverses the object hierarchy to find an object member specified by the identifier.
576-
*
582+
*
577583
* @param obj Root object to search in.
578584
* @param identifier Complete identifier of the member to find, e.g. "document.location.href".
579585
* @returns A tuple containing the immediate parent of the member and the member name.
@@ -586,19 +592,19 @@ export module DotNet {
586592
// Error handling in case of undefined last key depends on the type of operation.
587593
for (let i = 0; i < keys.length - 1; i++) {
588594
const key = keys[i];
589-
595+
590596
if (current && typeof current === 'object' && key in current) {
591597
current = current[key];
592598
} else {
593599
throw new Error(`Could not find '${identifier}' ('${key}' was undefined).`);
594600
}
595601
}
596-
602+
597603
return [current, keys[keys.length - 1]];
598604
}
599605

600606
/** Takes an object member and a call type and returns a function that performs the operation specified by the call type on the member.
601-
*
607+
*
602608
* @param parent Immediate parent of the accessed object member.
603609
* @param memberName Name (key) of the accessed member.
604610
* @param callType The type of the operation to perform on the member.
@@ -640,50 +646,50 @@ export module DotNet {
640646
if (!(propName in obj)) {
641647
return false;
642648
}
643-
649+
644650
// If the property is present we examine its descriptor, potentially needing to walk up the prototype chain.
645651
while (obj !== undefined) {
646652
const descriptor = Object.getOwnPropertyDescriptor(obj, propName);
647-
653+
648654
if (descriptor) {
649655
// Return true for data property
650656
if (descriptor.hasOwnProperty('value')) {
651657
return true
652658
}
653-
659+
654660
// Return true for accessor property with defined getter.
655661
return descriptor.hasOwnProperty('get') && typeof descriptor.get === 'function';
656662
}
657-
663+
658664
obj = Object.getPrototypeOf(obj);
659665
}
660-
666+
661667
return false;
662668
}
663-
669+
664670
function isWritableProperty(obj: any, propName: string) {
665671
// Return true for missing property if the property can be added.
666672
if (!(propName in obj)) {
667673
return Object.isExtensible(obj);
668674
}
669-
675+
670676
// If the property is present we examine its descriptor, potentially needing to walk up the prototype chain.
671677
while (obj !== undefined) {
672678
const descriptor = Object.getOwnPropertyDescriptor(obj, propName);
673-
679+
674680
if (descriptor) {
675681
// Return true for writable data property.
676682
if (descriptor.hasOwnProperty('value') && descriptor.writable) {
677683
return true;
678684
}
679-
685+
680686
// Return true for accessor property with defined setter.
681687
return descriptor.hasOwnProperty('set') && typeof descriptor.set === 'function';
682688
}
683-
689+
684690
obj = Object.getPrototypeOf(obj);
685691
}
686-
692+
687693
return false;
688694
}
689695

src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,4 +395,25 @@ describe("CallDispatcher", () => {
395395

396396
expect(result2).toBe("30");
397397
});
398-
});
398+
399+
test("createJSObjectReference: Handles null values without throwing", () => {
400+
const nullRef = DotNet.createJSObjectReference(null);
401+
expect(nullRef).toEqual({ [jsObjectId]: -1 });
402+
});
403+
404+
test("createJSObjectReference: Handles undefined values without throwing", () => {
405+
const undefinedRef = DotNet.createJSObjectReference(undefined);
406+
expect(undefinedRef).toEqual({ [jsObjectId]: -1 });
407+
});
408+
409+
test("disposeJSObjectReference: Safely handles null reference disposal", () => {
410+
const nullRef = DotNet.createJSObjectReference(null);
411+
expect(() => DotNet.disposeJSObjectReference(nullRef)).not.toThrow();
412+
});
413+
414+
test("createJSObjectReference: Still throws for invalid types", () => {
415+
expect(() => DotNet.createJSObjectReference("string")).toThrow();
416+
expect(() => DotNet.createJSObjectReference(123)).toThrow();
417+
expect(() => DotNet.createJSObjectReference(true)).toThrow();
418+
});
419+
});

src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSObjectReferenceJsonConverter.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ public override bool CanConvert(Type typeToConvert)
2222
public override IJSObjectReference? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
2323
{
2424
var id = JSObjectReferenceJsonWorker.ReadJSObjectReferenceIdentifier(ref reader);
25+
26+
if (id == -1)
27+
{
28+
return null;
29+
}
30+
2531
return new JSObjectReference(_jsRuntime, id);
2632
}
2733

src/JSInterop/Microsoft.JSInterop/test/Infrastructure/JSObjectReferenceJsonConverterTest.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,17 @@ public void Write_WritesValidJson()
8787
// Assert
8888
Assert.Equal($"{{\"__jsObjectId\":{jsObjectRef.Id}}}", json);
8989
}
90+
91+
[Fact]
92+
public void Read_ReturnsNull_WhenJSObjectIdIsMinusOne()
93+
{
94+
// Arrange
95+
var json = "{\"__jsObjectId\":-1}";
96+
97+
// Act
98+
var deserialized = JsonSerializer.Deserialize<IJSObjectReference>(json, JsonSerializerOptions);
99+
100+
// Assert
101+
Assert.Null(deserialized);
102+
}
90103
}

0 commit comments

Comments
 (0)