Skip to content

Commit

Permalink
Fix ThrowIfMissing to allow making arbitrary fields optional (#177)
Browse files Browse the repository at this point in the history
  • Loading branch information
agocke authored Jul 4, 2024
1 parent 39275e8 commit f9db8d4
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/generator/ConfigOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal record TypeOptions
internal readonly record struct MemberOptions()
{
/// <see cref="SerdeMemberOptions.ThrowIfMissing" />
public bool ThrowIfMissing { get; init; } = false;
public bool? ThrowIfMissing { get; init; } = null;

/// <see cref="SerdeMemberOptions.Rename" />
public string? Rename { get; init; } = null;
Expand Down
2 changes: 1 addition & 1 deletion src/generator/DataMemberSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ static bool IsNullable(ISymbol symbol)

public bool SkipSerialize => _memberOptions.SkipSerialize;

public bool ThrowIfMissing => _memberOptions.ThrowIfMissing;
public bool? ThrowIfMissing => _memberOptions.ThrowIfMissing;

public bool SerializeNull => _memberOptions.SerializeNull ?? _typeOptions.SerializeNull;

Expand Down
5 changes: 4 additions & 1 deletion src/generator/Generator.Deserialize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,10 @@ private static MethodDeclarationSyntax GenerateCustomDeserializeMethod(
{AssignedVarName} |= (({assignedVarType})1) << {i};
break;
""");
if (!m.IsNullable || m.ThrowIfMissing)

// Require that the member is assigned if m.ThrowIfMissing is set, or if it is not nullable
// and ThrowIfMissing is unset
if (m.ThrowIfMissing == true || (!m.IsNullable && m.ThrowIfMissing == null))
{
assignedMaskValue |= 1L << i;
}
Expand Down
2 changes: 1 addition & 1 deletion src/serde/Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ sealed class SerdeMemberOptions : Attribute
/// default behavior for fields of non-nullable types, while the default behavior for nullable
/// types is to set the field to null.
/// </summary>
public bool ThrowIfMissing { get; init; } = false;
public bool ThrowIfMissing { get; init; } = true;

/// <summary>
/// Use the given name instead of the name of the field or property.
Expand Down
21 changes: 21 additions & 0 deletions test/Serde.Test/JsonDeserializeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,27 @@ public void ThrowIfMissing()
Assert.Throws<InvalidDeserializeValueException>(() => JsonSerializer.Deserialize<ThrowMissing>(src));
}

[GenerateDeserialize]
private partial record ThrowMissingFalse
{
public required string Present { get; init; }
[SerdeMemberOptions(ThrowIfMissing = false)]
public bool Missing { get; init; } = false;
}

[Fact]
public void ThrowIfMissingFalse()
{
var src = """
{
"present": "abc",
"extra": "def"
}
""";
var expected = new ThrowMissingFalse { Present = "abc", Missing = false };
Assert.Equal(expected, JsonSerializer.Deserialize<ThrowMissingFalse>(src));
}

[Fact]
public void DenyUnknownTest()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@

#nullable enable
using System;
using Serde;

namespace Serde.Test
{
partial class JsonDeserializeTests
{
partial record ThrowMissingFalse : Serde.IDeserialize<Serde.Test.JsonDeserializeTests.ThrowMissingFalse>
{
static Serde.Test.JsonDeserializeTests.ThrowMissingFalse Serde.IDeserialize<Serde.Test.JsonDeserializeTests.ThrowMissingFalse>.Deserialize(IDeserializer deserializer)
{
string _l_present = default !;
bool _l_missing = default !;
byte _r_assignedValid = 0;
var _l_typeInfo = ThrowMissingFalseSerdeTypeInfo.TypeInfo;
var typeDeserialize = deserializer.DeserializeType(_l_typeInfo);
int _l_index_;
while ((_l_index_ = typeDeserialize.TryReadIndex(_l_typeInfo, out var _l_errorName)) != IDeserializeType.EndOfType)
{
switch (_l_index_)
{
case 0:
_l_present = typeDeserialize.ReadValue<string, StringWrap>(_l_index_);
_r_assignedValid |= ((byte)1) << 0;
break;
case 1:
_l_missing = typeDeserialize.ReadValue<bool, BoolWrap>(_l_index_);
_r_assignedValid |= ((byte)1) << 1;
break;
case Serde.IDeserializeType.IndexNotFound:
break;
default:
throw new InvalidOperationException("Unexpected index: " + _l_index_);
}
}

if ((_r_assignedValid & 0b1) != 0b1)
{
throw new Serde.InvalidDeserializeValueException("Not all members were assigned");
}

var newType = new Serde.Test.JsonDeserializeTests.ThrowMissingFalse()
{
Present = _l_present,
Missing = _l_missing,
};
return newType;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Serde.Test;
partial class JsonDeserializeTests
{
internal static class ThrowMissingFalseSerdeTypeInfo
{
internal static readonly Serde.TypeInfo TypeInfo = Serde.TypeInfo.Create(
"ThrowMissingFalse",
Serde.TypeInfo.TypeKind.CustomType,
new (string, System.Reflection.MemberInfo)[] {
("present", typeof(Serde.Test.JsonDeserializeTests.ThrowMissingFalse).GetProperty("Present")!),
("missing", typeof(Serde.Test.JsonDeserializeTests.ThrowMissingFalse).GetProperty("Missing")!)
});
}
}

0 comments on commit f9db8d4

Please sign in to comment.