Search code examples
c#serializationservicestackormlite-servicestack

Make ORMLite use proper serialization for structs


tl;dr:

I am registering a serializer and a deserializer on a struct.
The serializer is not called, but the deserializer is.

How can I fix this?
It works properly on reference types, and doing JsConfig<Position>.TreatValueAsRefType = true; did not help either.


Long version:

I am storing two complex types using ORMLite: Position (a struct, from external library DotSpatial which we do not control) and Tuple.

In order to be able to properly store/read them from the database, I defined their serializers and deserializers:

// Struct. Called by position.ToJsv(), NOT called by ORMLite's connection.Insert() .
JsConfig<Position>.SerializeFn = position =>
{
    string str = position.ToString(null, CultureInfo.InvariantCulture);
    return str; // Breakpoint here.
};
// Struct. Called in both.
JsConfig<Position>.DeSerializeFn = position => Position.Parse(position, CultureInfo.InvariantCulture);

// Reference type. Works fine.
JsConfig<Tuple<double, double>>.SerializeFn = tuple => string.Format(CultureInfo.InvariantCulture,
    "{0}{1}{2}",
    tuple.Item1, CultureInfo.InvariantCulture.TextInfo.ListSeparator, tuple.Item2
    );
// Works fine too.
JsConfig<Tuple<double, double>>.DeSerializeFn = tuple =>
{
    var values = tuple.Split(new[] { CultureInfo.InvariantCulture.TextInfo.ListSeparator }, StringSplitOptions.None);
    double item1, item2;
    if (values.Length == 2
        && double.TryParse(values[0], out item1)
        && double.TryParse(values[1], out item2))
    {
        var result = new Tuple<double, double>(item1, item2);
        return result;
    }
    throw new ArgumentException("Could not parse easting and northing from database; malformatted?", "tuple");
};

Debugging

A break-point in the deserializer is hit when reading from the DB with ORMLite: connection.Where<T>(item => item.Foo == bar).
 break-point in the serializer is not hit when writing to the DB with ORMLite: connection.Insert(item).

I thought maybe the serializer was not being registered properly, so I called .ToJsv() on the object.

var lat = Latitude.Parse("00°00'02.7451\"N", CultureInfo.InvariantCulture);
var lon = Longitude.Parse("013°29'17.3270\"W", CultureInfo.InvariantCulture);
Position pos = new Position(lat, lon);
string foo = pos.ToJsv(); // Works, hits the breakpoint.

When hitting the breakpoint, str = 00°00'02.7451"N,013°29'17.3270"W.
But when inserting with ORMLite, the breakpoint is not hit and I get values in the database such as 00°00'02,7451"N;013°29'17,3270"W - note the commas, due to the culture.

The database is saving culture-dependent values! :(

Attempts

Since this happens only on structs, I tried to register the type to be treated as a reference type, but that did not seem to work.

JsConfig<Position>.TreatValueAsRefType = true;

Update:

I am using the ORMLite.PostgreSQL Nuget package (v 3.9.70). It includes ServiceStack.Text (v 3.9.70) and Npgsql (v 2.0.11).

I want to try getting the code from source control and debugging it directly, but for now I don't have time.

The Position struct is defined in an external library, which I cannot change.

Minimalist sample

I have uploaded a minimalist sample at https://gist.github.com/aneves/7830776 , which shows the following output:

Thing, current culture: 12,6;10,9
Thing, invariant culture: 12.6,10.9
Thing, from Jsv: "12,6;10,9"
>> deserializing 10;35
>> Could not parse value, it is malformed. (10;35)
Found this: Box[A: 0;0]
Press any key to continue . . .

Solution

  • UPDATE:

    After checking the source code of OrmLite on GitHub, it appeared that:

    • JsConfig<Position>.TreatValueAsRefType would never be checked when serializing data into the database.
    • For deserialization OrmLite would always call TypeSerializer.DeserializeFromString, this is why your scenario worked in that direction only.

    To fix the issue, I have submitted a patch to the master repository. In the mean time you are most welcome to either recompile OrmLite using this patch, or simply use the recompiled version (based on 4.0.3) I have made available for you here in lieu of one of the corresponding file from NuGet.

    I hope this fix will be incorporated in the next official release, and also in the 3.* branch.

    ORIGINAL ANSWER:

    If you have control over the Position struct (which seems not to be the case), have you tried overriding ToString()? OrmLite should call it if I remember correctly:

    struct Position {
        public override ToString(object o, CultureInfo culture) {
            /* Your serialization */
        }
    
        public override ToString() { // Will be used by OrmLite to serialize
            position.ToString(null, CultureInfo.InvariantCulture);
        }
    }
    

    It may not solve SerializeFn<> not being called but could be good enough for your objective, at least until the bug is fixed.