Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions src/HotChocolate/Core/src/Types/Types/InputParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -614,14 +614,6 @@ private object DeserializeObject(object resultValue, InputObjectType type, Path
}

object? value = null;

// if the type is nullable but the runtime type is a non-nullable value
// we will create a default instance and assign that instead.
if (field.RuntimeType.IsValueType)
{
value = Activator.CreateInstance(field.RuntimeType);
}

return field.IsOptional
? new Optional(value, false)
: value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,11 @@ private static void CompileSetProperties<T>(
{
value = CreateOptional(value, field.RuntimeType);
}
else if (field.Property.PropertyType.IsValueType
&& System.Nullable.GetUnderlyingType(field.Property.PropertyType) == null)
{
value = Expression.Coalesce(value, Expression.Default(field.Property.PropertyType));
}

value = Expression.Convert(value, field.Property.PropertyType);
Expression setPropertyValue = Expression.Call(instance, setter, value);
Expand Down
60 changes: 60 additions & 0 deletions src/HotChocolate/Core/test/Types.Tests/Types/InputParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,55 @@ public void Force_NonNull_Struct_To_Be_Optional()
Assert.IsType<Test4Input>(runtimeValue).MatchSnapshot();
}

[Fact]
public async Task Integration_CodeFirst_InputObjectNoDefaultValue_NoRuntimeTypeDefaultValueIsInitialized()
{
// arrange
var resolverArgumentsAccessor = new ResolverArgumentsAccessor();
var executor = await new ServiceCollection()
.AddSingleton(resolverArgumentsAccessor)
.AddGraphQL()
.AddQueryType(x => x.Field("foo")
.Argument("args", a => a.Type<NonNullType<MyInputType>>())
.Type<StringType>()
.ResolveWith<ResolverArgumentsAccessor>(r => r.ResolveWith(default!)))
.BuildRequestExecutorAsync();

// act
var query =
OperationRequest.FromSourceText(
"""
{
a: foo(args: { string: "allSet" int: 1 bool: true })
b: foo(args: { string: "noneSet" })
c: foo(args: { string: "intExplicitlyNull" int: null })
d: foo(args: { string: "boolExplicitlyNull" bool: null })
e: foo(args: { string: "intSetBoolNull" int: 1 bool: null })
f: foo(args: { string: "boolSetIntNull" int: null bool: true })
}
""");
await executor.ExecuteAsync(query, CancellationToken.None);

// assert
resolverArgumentsAccessor.Arguments.MatchSnapshot();
}

private class ResolverArgumentsAccessor
{
private readonly object _lock = new();
internal SortedDictionary<string, IDictionary<string, object?>?> Arguments { get; } = new();

internal string? ResolveWith(IDictionary<string, object?> args)
{
lock (_lock)
{
Arguments[args["string"]!.ToString()!] = args;
}

return "OK";
}
}

public class TestInput
{
public string? Field1 { get; set; }
Expand Down Expand Up @@ -564,4 +613,15 @@ public class Test4Input

public int Field2 { get; set; }
}

public class MyInputType : InputObjectType
{
protected override void Configure(IInputObjectTypeDescriptor descriptor)
{
descriptor.Name("MyInput");
descriptor.Field("string").Type<StringType>();
descriptor.Field("int").Type<IntType>();
descriptor.Field("bool").Type<BooleanType>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"allSet": {
"string": "allSet",
"int": 1,
"bool": true
},
"boolExplicitlyNull": {
"string": "boolExplicitlyNull",
"int": null,
"bool": null
},
"boolSetIntNull": {
"string": "boolSetIntNull",
"int": null,
"bool": true
},
"intExplicitlyNull": {
"string": "intExplicitlyNull",
"int": null,
"bool": null
},
"intSetBoolNull": {
"string": "intSetBoolNull",
"int": 1,
"bool": null
},
"noneSet": {
"string": "noneSet",
"int": null,
"bool": null
}
}