mfenniak / rethinkdb-net

A C# / .NET client driver for RethinkDB.
Other
247 stars 37 forks source link

What is the reasoning behind custom Json Serializer? #192

Closed oliverjanik closed 9 years ago

oliverjanik commented 9 years ago

Just asking. Interested to know.

Also does it support dynamic or Dictionary types?

dragan commented 9 years ago

@oliverjanik You can learn the reasoning behind it in #175.

oliverjanik commented 9 years ago

So basically to avoid a 3rd party dependency. How I wish for a standardized JSON impl in .NET that doesn't suck.

If I understand correctly this library doesn't support dictionaries or dynamic, which is a shame given the schema-less nature of RethinkDB

mfenniak commented 9 years ago

The custom implementation of a JSON serializer that exists in rethinkdb-net is purely, 100% for the internal implementation of the RethinkDB protocol. It doesn't need dictionaries or dynamic objects because RethinkDB talks a very specific protocol.

It sounds like you're more concerned with how RethinkDB data gets represented as .NET objects. That is unrelated to the internal JSON serializer.

To convert RethinkDB data to .NET objects, we have an interface called IDatumConverterFactory. This is a factory that provides the capability to create datum converters for specific types. A datum converter is an object (IDatumConverter) that converts between RethinkDB's representation of data and .NET objects.

There are two major implementations of IDatumConverterFactory in rethinkdb-net. One is built into the rethinkdb-net.dll assembly, and it's called DataContractDatumConverterFactory. This converts RethinkDB key/value pairs into objects that are marked up as [DataContracts]; it's pretty fixed and useful for well-known schemas with little flexibility. But, it requires no third-party dependencies, so it's integrated straight into our library. It's also fast.

The second major implementation of IDatumConverterFactory is provided by a secondary assembly, rethinkdb-net-newtonsoft. This library provides integration with the fairly standard Json.NET library, and allows you to define objects in the manner that you normally would when working with that library. It then converts back and forth between those objects and RethinkDB key/value pairs in a manner that's consistent with how a Json.NET library user would expect. There's some documentation on this over here: https://github.com/mfenniak/rethinkdb-net/wiki/Newtonsoft-Serialization

It's also quite possible (theoretically, at least) to write an IDatumConverterFactory that represents RethinkDB key/value pairs as a dictionary, or some other .NET type. Dictionary would in-fact be easy, but, the problem that would be run into is how we convert .NET lambdas into RethinkDB queries wouldn't support dictionaries very well. eg. table.OrderBy(o => o.Name) wouldn't work with a table being represented as a dictionary; it would need to be something like table.OrderBy(d => d['Name']), and then our lambda->query converter would need updates to support this syntax.

So, the TL;DR: the internal JSON doesn't affect users of the library; we are flexible about how we convert from RethinkDB key/value pairs to .NET objects.

oliverjanik commented 9 years ago

Thanks for the comprehensive answer. I didn't realize your internal json parser is not used for user classes.

Getting Dict lookups or even dynamic supported in expression trees is challenging but not impossible.

Dictionary or dynamic would only cover one of my use cases. The other one is polymorphic attribute whose structure depends on the discrimantor field.

mfenniak commented 9 years ago

If I'm understanding your terminology correctly, a polymorphic usage would be possible as well. At a high-level:

  1. Establish a base-type (class/interface) that you'd like to work with (eg. interface IThingy).
  2. Create a table object that returns that base class (eg. Query.Db("test").Table<IThingy>("thingies")).
  3. Create an IDatumConverterFactory that is capable of creating IThingy objects from RethinkDB key/values. It would presumably inspect the RethinkDB values and create the right concrete type based upon the discriminator field.
  4. Modify the QueryConverter property on your connection to use your new IDatumConverterFactory. The default QueryConverter is constructed with this code-snippet below; basically you'd want to remove DataContractDatumConverterFactory.Instance and add an instance of your new IDatumConverterFactory subclass instead.
QueryConverter = new QueryConverter(
    new AggregateDatumConverterFactory(
        PrimitiveDatumConverterFactory.Instance,
        DataContractDatumConverterFactory.Instance,
        DateTimeDatumConverterFactory.Instance,
        DateTimeOffsetDatumConverterFactory.Instance,
        GuidDatumConverterFactory.Instance,
        UriDatumConverterFactory.Instance,
        TupleDatumConverterFactory.Instance,
        BinaryDatumConverterFactory.Instance,
        ArrayDatumConverterFactory.Instance,
        AnonymousTypeDatumConverterFactory.Instance,
        BoundEnumDatumConverterFactory.Instance,
        EnumDatumConverterFactory.Instance,
        NullableDatumConverterFactory.Instance,
        ListDatumConverterFactory.Instance,
        TimeSpanDatumConverterFactory.Instance,
        GroupingDictionaryDatumConverterFactory.Instance
    ),
    new Expressions.DefaultExpressionConverterFactory()
);

The reference here to DefaultExpressionConverterFactory would also be where it is possible to customize how lambda expressions are converted into RethinkDB queries. Where datum converters map data between .NET and RethinkDB, the expression converters map System.Linq.Expression trees into RethinkDB queries. This theoretically provides the customizability to do the dictionary-lookup queries, dynamic lookup, and anything else you could think of. For example, you could add a method to your IThingy interface, call it from within a lambda expression, and have an expression converter translate it into a query term that would run on the RethinkDB database.

oliverjanik commented 9 years ago

Again, thank you for comprehensive answer.I'll dive into the converters when I get the chance.