Skip to content
This repository has been archived by the owner on Jun 26, 2024. It is now read-only.

Empty list serialization #6

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "2.1.500"
"version": "2.1.818"
matt-whitfield-scopely marked this conversation as resolved.
Show resolved Hide resolved
},
"msbuild-sdks": {
"MSBuild.Sdk.Extras": "1.5.4",
Expand Down
173 changes: 173 additions & 0 deletions src/protobuf-net.Test/Meta/Lists.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,53 @@ public void RoundTripTypedList_String()
Assert.True(obj.ListString.SequenceEqual(clone.ListString));
}

[Fact]
public void RoundTripTypedList_String_Null()
{
var model = TypeModel.Create();
model.Add(typeof(TypeWithLists), false).Add(1, "ListString");
TypeWithLists obj = new TypeWithLists();
obj.ListString = null;

TypeWithLists clone = (TypeWithLists)model.DeepClone(obj);
Assert.NotNull(clone);
Assert.Null(clone.ListString);

model.CompileInPlace();
clone = (TypeWithLists)model.DeepClone(obj);
Assert.NotNull(clone);
Assert.Null(clone.ListString);

clone = (TypeWithLists)model.Compile().DeepClone(obj);
Assert.NotNull(clone);
Assert.Null(clone.ListString);
}

[Fact]
public void RoundTripTypedList_String_Empty()
{
var model = TypeModel.Create();
model.Add(typeof(TypeWithLists), false).Add(1, "ListString");
TypeWithLists obj = new TypeWithLists();
obj.ListString = new List<string>();

TypeWithLists clone = (TypeWithLists)model.DeepClone(obj);
Assert.NotNull(clone);
Assert.NotNull(clone.ListString);
Assert.Empty(clone.ListString);

model.CompileInPlace();
clone = (TypeWithLists)model.DeepClone(obj);
Assert.NotNull(clone);
Assert.NotNull(clone.ListString);
Assert.Empty(clone.ListString);

clone = (TypeWithLists)model.Compile().DeepClone(obj);
Assert.NotNull(clone);
Assert.NotNull(clone.ListString);
Assert.Empty(clone.ListString);
}

[Fact]
public void RoundTripTypedIList_String()
{
Expand Down Expand Up @@ -151,6 +198,53 @@ public void RoundTripTypedIList_String()
Assert.True(obj.IListStringTyped.SequenceEqual(clone.IListStringTyped));
}

[Fact]
public void RoundTripTypedIList_String_Null()
{
var model = TypeModel.Create();
model.Add(typeof(TypeWithLists), false).Add(2, "IListStringTyped");
TypeWithLists obj = new TypeWithLists();
obj.IListStringTyped = null;

TypeWithLists clone = (TypeWithLists)model.DeepClone(obj);
Assert.NotNull(clone);
Assert.Null(clone.IListStringTyped);

model.CompileInPlace();
clone = (TypeWithLists)model.DeepClone(obj);
Assert.NotNull(clone);
Assert.Null(clone.IListStringTyped);

clone = (TypeWithLists)model.Compile().DeepClone(obj);
Assert.NotNull(clone);
Assert.Null(clone.IListStringTyped);
}

[Fact]
public void RoundTripTypedIList_String_Empty()
{
var model = TypeModel.Create();
model.Add(typeof(TypeWithLists), false).Add(2, "IListStringTyped");
TypeWithLists obj = new TypeWithLists();
obj.IListStringTyped = new List<string>();

TypeWithLists clone = (TypeWithLists)model.DeepClone(obj);
Assert.NotNull(clone);
Assert.NotNull(clone.IListStringTyped);
Assert.True(obj.IListStringTyped.Count == 0);

model.CompileInPlace();
clone = (TypeWithLists)model.DeepClone(obj);
Assert.NotNull(clone);
Assert.NotNull(clone.IListStringTyped);
Assert.True(obj.IListStringTyped.Count == 0);

clone = (TypeWithLists)model.Compile().DeepClone(obj);
Assert.NotNull(clone);
Assert.NotNull(clone.IListStringTyped);
Assert.True(obj.IListStringTyped.Count == 0);
}


[Fact]
public void RoundTripArrayList_String()
Expand Down Expand Up @@ -315,6 +409,85 @@ public void RoundTripIList_Int32()
Assert.True(obj.IListInt32Untyped.Cast<int>().SequenceEqual(clone.IListInt32Untyped.Cast<int>()));
}

[Fact]
public void RoundTripArray_String()
{
var model = TypeModel.Create();
model.Add(typeof(Arrays), false).Add(4, "BasicArray");
Arrays obj = new Arrays();
obj.BasicArray = new[] { "abc", "def" };

Arrays clone = (Arrays)model.DeepClone(obj);
Assert.NotNull(clone);
Assert.NotNull(clone.BasicArray);
Assert.True(obj.BasicArray.SequenceEqual(clone.BasicArray));

model.CompileInPlace();
clone = (Arrays)model.DeepClone(obj);
Assert.NotNull(clone);
Assert.NotNull(clone.BasicArray);
Assert.True(obj.BasicArray.SequenceEqual(clone.BasicArray));

clone = (Arrays)model.Compile().DeepClone(obj);
Assert.NotNull(clone);
Assert.NotNull(clone.BasicArray);
Assert.True(obj.BasicArray.SequenceEqual(clone.BasicArray));
}


[Fact]
public void RoundTripArray_String_Null()
{
var model = TypeModel.Create();
model.Add(typeof(Arrays), false).Add(4, "BasicArray");
Arrays obj = new Arrays();
obj.BasicArray = null;

Arrays clone = (Arrays)model.DeepClone(obj);
Assert.NotNull(clone);
Assert.Null(clone.BasicArray);

model.CompileInPlace();
clone = (Arrays)model.DeepClone(obj);
Assert.NotNull(clone);
Assert.Null(clone.BasicArray);

clone = (Arrays)model.Compile().DeepClone(obj);
Assert.NotNull(clone);
Assert.Null(clone.BasicArray);
}


[Fact]
public void RoundTripArray_String_Empty()
{
var model = TypeModel.Create();
model.Add(typeof(Arrays), false).Add(4, "BasicArray");
Arrays obj = new Arrays();
obj.BasicArray = new string[0];

Arrays clone = (Arrays)model.DeepClone(obj);
Assert.NotNull(clone);
Assert.NotNull(clone.BasicArray);
Assert.True(obj.BasicArray.Length == 0);

model.CompileInPlace();
clone = (Arrays)model.DeepClone(obj);
Assert.NotNull(clone);
Assert.NotNull(clone.BasicArray);
Assert.True(obj.BasicArray.Length == 0);

clone = (Arrays)model.Compile().DeepClone(obj);
Assert.NotNull(clone);
Assert.NotNull(clone.BasicArray);
Assert.True(obj.BasicArray.Length == 0);
}

public class Arrays
{
public string[] BasicArray { get; set; }
}

public class NastyType
{

Expand Down
37 changes: 36 additions & 1 deletion src/protobuf-net.Test/Serializers/IReadOnlyCollectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class IReadOnlyCollectionTests
[Fact]
public void BasicIReadOnlyCollectionTest()
{
var orig = new TypeWithIReadOnlyCollection { Items = new List<string>{"abc", "def"} };
var orig = new TypeWithIReadOnlyCollection { Items = new List<string> { "abc", "def" } };
var model = TypeModel.Create();
var clone = (TypeWithIReadOnlyCollection)model.DeepClone(orig);
Assert.Equal(orig.Items, clone.Items); //, "Runtime");
Expand All @@ -22,6 +22,41 @@ public void BasicIReadOnlyCollectionTest()
Assert.Equal(orig.Items, clone.Items); //, "Compile");
}

[Fact]
public void NullIReadOnlyCollectionTest()
{
var orig = new TypeWithIReadOnlyCollection { Items = null };
var model = TypeModel.Create();
var clone = (TypeWithIReadOnlyCollection)model.DeepClone(orig);
Assert.Null(clone.Items);

model.CompileInPlace();
clone = (TypeWithIReadOnlyCollection)model.DeepClone(orig);
Assert.Null(clone.Items);

clone = (TypeWithIReadOnlyCollection)model.Compile().DeepClone(orig);
Assert.Null(clone.Items);
}

[Fact]
public void EmptyIReadOnlyCollectionTest()
{
var orig = new TypeWithIReadOnlyCollection { Items = new List<string>() };
var model = TypeModel.Create();
var clone = (TypeWithIReadOnlyCollection)model.DeepClone(orig);
Assert.NotNull(clone.Items);
Assert.True(clone.Items.Count == 0);

model.CompileInPlace();
clone = (TypeWithIReadOnlyCollection)model.DeepClone(orig);
Assert.NotNull(clone.Items);
Assert.True(clone.Items.Count == 0);

clone = (TypeWithIReadOnlyCollection)model.Compile().DeepClone(orig);
Assert.NotNull(clone.Items);
Assert.True(clone.Items.Count == 0);
}

[ProtoContract]
public class TypeWithIReadOnlyCollection
{
Expand Down
41 changes: 41 additions & 0 deletions src/protobuf-net.Test/Serializers/KeyValuePairTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,47 @@ public void TypeWithDictionaryTest()
Assert.Equal(123.45M, clone.Data["abc"]); //, "Runtime");
}

[Fact]
public void TypeWithDictionaryNullTest()
{
var orig = new TypeWithDictionary { Data = null };
var model = TypeModel.Create();
var clone = (TypeWithDictionary)model.DeepClone(orig);
Assert.Null(clone.Data);

model.Compile("TypeWithDictionaryTest", "TypeWithDictionaryTest.dll");
PEVerify.Verify("TypeWithDictionaryTest.dll");

model.CompileInPlace();
clone = (TypeWithDictionary)model.DeepClone(orig);
Assert.Null(clone.Data);

clone = (TypeWithDictionary)model.Compile().DeepClone(orig);
Assert.Null(clone.Data);
}

[Fact]
public void TypeWithDictionaryEmptyTest()
{
var orig = new TypeWithDictionary { Data = new Dictionary<string, decimal>() };
var model = TypeModel.Create();
var clone = (TypeWithDictionary)model.DeepClone(orig);
Assert.NotNull(clone.Data);
Assert.True(clone.Data.Count == 0);

model.Compile("TypeWithDictionaryTest", "TypeWithDictionaryTest.dll");
PEVerify.Verify("TypeWithDictionaryTest.dll");

model.CompileInPlace();
clone = (TypeWithDictionary)model.DeepClone(orig);
Assert.NotNull(clone.Data);
Assert.True(clone.Data.Count == 0);

clone = (TypeWithDictionary)model.Compile().DeepClone(orig);
Assert.NotNull(clone.Data);
Assert.True(clone.Data.Count == 0);
}

[Fact]
public void ShouldWorkWithAutoLoadDisabledRuntime()
{
Expand Down
4 changes: 2 additions & 2 deletions src/protobuf-net.Test/protobuf-net.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
<PropertyGroup>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<!--<TargetFrameworks>netcoreapp1.1;net452</TargetFrameworks>-->
<TargetFrameworks>net452</TargetFrameworks>
<TargetFrameworks>netcoreapp2.1</TargetFrameworks>
<LibImport>net</LibImport>
<Configurations>Debug;Release;VS</Configurations>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework)=='netcoreapp1.1'">
<PropertyGroup Condition="$(TargetFramework)=='netcoreapp2.1'">
<DefineConstants>FEAT_COMPILER;NO_NHIBERNATE;COREFX;NO_INTERNAL_CONTEXT</DefineConstants>
<LibImport>core</LibImport>
</PropertyGroup>
Expand Down
11 changes: 11 additions & 0 deletions src/protobuf-net/Compiler/CompilerContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,12 @@ public void LoadReaderWriter()
Emit(isStatic ? OpCodes.Ldarg_1 : OpCodes.Ldarg_2);
}

public void SetTo1(Local local)
{
il.Emit(OpCodes.Ldc_I4_1);
StoreValue(local);
}

public void StoreValue(Local local)
{
if (local == this.InputValue)
Expand Down Expand Up @@ -1237,6 +1243,11 @@ internal void Add()
Emit(OpCodes.Add);
}

internal void And()
{
Emit(OpCodes.And);
}

internal void LoadLength(Local arr, bool zeroIfNull)
{
Helpers.DebugAssert(arr.Type.IsArray && arr.Type.GetArrayRank() == 1);
Expand Down
1 change: 1 addition & 0 deletions src/protobuf-net/Meta/TypeModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1386,6 +1386,7 @@ public object DeepClone(object value, bool existingValue = false)
writer.Close();
}
ms.Position = 0;

ProtoReader reader = null;
try
{
Expand Down
5 changes: 5 additions & 0 deletions src/protobuf-net/ProtoReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ public sealed class ProtoReader : IDisposable
/// </summary>
public int FieldNumber => fieldNumber;

/// <summary>
/// Returns true if the field being processed is an empty list.
/// </summary>
public bool IsEmptyList => (fieldNumber & Serializer.EmptyListFlag) == Serializer.EmptyListFlag;

/// <summary>
/// Indicates the underlying proto serialization format on the wire.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions src/protobuf-net/ProtoWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@ internal static void WriteHeaderCore(int fieldNumber, WireType wireType, ProtoWr
WriteUInt32Variant(header, writer);
}

public static void WriteEmptyList(int fieldNumber, ProtoWriter writer)
{
ProtoWriter.WriteFieldHeader(fieldNumber | Serializer.EmptyListFlag, WireType.String, writer);
ProtoWriter.WriteString("", writer);
}

/// <summary>
/// Writes a byte-array to the stream; supported wire-types: String
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions src/protobuf-net/Serializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ namespace ProtoBuf
/// </remarks>
public static class Serializer
{
public const int EmptyListFlag = 0x01000000;

#if !NO_RUNTIME
/// <summary>
/// Suggest a .proto definition for the given type
Expand Down
Loading