akkadotnet / Hyperion

Polymorphic serialization for .NET
Apache License 2.0
277 stars 62 forks source link

Hyperion failed with "Index was out of range" in `GetDeserializedObject` when payload has Duplicate Values #52

Open daniellittledev opened 7 years ago

daniellittledev commented 7 years ago

My payload contains data like this in an FSharp Record. The error goes away if the values if the values are different. Only occurs when preserve object refs is true.

    Section = Some "sd"
    SubDivision = Some "sd"

This is the exception.

$exception  {"Index was out of range. Must be non-negative and less than the size of the collection.\r\nParameter name: index"} System.ArgumentOutOfRangeException

Here is the line that fails.

public class DeserializerSession
{
    ////
    public object GetDeserializedObject(int id)
    {
        return _objectById[id]; // <- here
    }
    ////

Stack Trace

>   Hyperion.dll!Hyperion.DeserializerSession.GetDeserializedObject(int id = 14) Line 66    C#
    Hyperion.dll!Hyperion.ValueSerializers.ObjectReferenceSerializer.ReadValue(System.IO.Stream stream = {System.IO.MemoryStream}, Hyperion.DeserializerSession session = {Hyperion.DeserializerSession}) Line 34   C#
    Hyperion.dll!Hyperion.Extensions.StreamEx.ReadObject(System.IO.Stream stream = {System.IO.MemoryStream}, Hyperion.DeserializerSession session = {Hyperion.DeserializerSession}) Line 172    C#
tvjames commented 7 years ago

Here's a failing test in F#, it looks like the root cause is the inclusion of the F# Map type in the Header type of the Payload record.

type Header = {
    Properties: Map<int,int>;
}

type Payload<'t> = 
    {
        Header: Header;
        Value: 't
    }
    override this.ToString() =
        "Envelope: " + this.Value.ToString()

type InnerType = {
    OneField : string option
    TwoField : string option 
}
type OuterType = {
    Something : InnerType
    FirstField : string option
    SecondField : string option 
    ThirdField : string option 
}

let inner = {
    OneField = Some "value"
    TwoField = Some "value"
}

let toSerialise = {
    Header = { Properties = Map.empty; }
    Value = inner
}

toSerialise |> Dump |> ignore 

let options = Hyperion.SerializerOptions(preserveObjectReferences = true)
let serialiser = Hyperion.Serializer(options)

let ``run test`` = 
    use inbound = new MemoryStream()
    serialiser.Serialize(toSerialise, inbound)
    inbound.Flush()

    use outbound = new MemoryStream(inbound.ToArray())
    let result = serialiser.Deserialize outbound

    result

``run test`` |> Dump |> ignore 

Ran in LinqPad, using the latest Hyperion from nuget