Registering exporters for interfaces

25 views
Skip to first unread message

Vish

unread,
Feb 24, 2009, 4:02:25 PM2/24/09
to Jayrock
Hi,

I am trying to register an exporter for one of my interfaces but it
doesn't seem to respect it. Looking at the code, it does not seem like
registering exporters for interface would be found properly by the
FindExporter method. Is there a reason why I shouldn't register
exporters for interfaces? If not, how best can I enable it easily?

Also, I am trying to unit test my exporters and I have a App.config
file in the test project with the exporters added there. Even though
the System.Configuration.ConfigurationSetting seems to read the
section properly, The FindExporter method on the ExportContext is
still not returning the correct exporter for my class. If call
register manually, the FindExporter finds it. Any ideas as to why?

I would appreciate any thoughts or suggestions regarding the issues
above.

Thank You,
Vish

Atif Aziz

unread,
Feb 25, 2009, 2:48:01 PM2/25/09
to jay...@googlegroups.com
> it does not seem like
> registering exporters for interface

You are right. Exporters for interfaces are not supported. In general, interfaces are wide types that represent protocols and so the chances of wrongly representing a "class-type" are high. Nonetheless, you can achieve the effect through the use of generics. Here's how. Say you have an interface like this:

interface IFoo
{
string Text { get; }
}

An exporter for types implementing this interface can then be implemented as a generic exporter like this:

Public class FooExporter<T> : IExporter where T : IFoo
{
public Type InputType { get { return typeof(T); } }

void IExporter.Export(ExportContext context,
object value, JsonWriter writer)
{
Export(context, (T) value, writer);
}

public virtual void Export(ExportContext context,
T value, JsonWriter writer)
{
writer.WriteStartObject();
writer.WriteMember("text");
context.Export(value.Text.ToUpperInvariant(), writer);
writer.WriteEndObject();
}
}

Now, say, you have two classes that implement IFoo called Bar and Baz:

class Bar : IFoo
{
public string Text { get { return "Bar"; } }
}

class Baz : IFoo
{
public string Text { get { return "Baz"; } }
}

You then register the exporter twice, once for Bar and once for Baz:

var context = new ExportContext();
context.Register(new FooExporter<Bar>());
context.Register(new FooExporter<Baz>());

Then you export as usual. I have attached a complete working example to this post. You can also find it here:
http://gist.github.com/70369

Hope this helps,
- Atif
InterfaceExportDemo.cs

Vish

unread,
Feb 27, 2009, 3:17:50 PM2/27/09
to Jayrock
Hi Atif,

Thank you for the code samples. I appreciate your help. I think it
would be real useful to be able to register interface types /abstract
class types for export (and may be import). When I export a class
implementing the interface using the registered exporter, me as a
developer will know that I am only serializing the interface specific
portion of the class. I will have the flexibility to have a class that
implements N interfaces and I can create N exporters to get a
representation of the same class is various formats or serialize only
specific parts of a class.

Also, registering FooExporter<BarA>, FooExporter<BarB>,
FooExporter<BarC> etc, in my opinion is hard to maintain and keep
updated. I think registering interfaces can actually be powerful.
Other than the fact that JayRock doesn't support it right now, do you
see any drawbacks in being able to do this. If there are no drawbacks,
I think I can update the FindType method called by FindExporter in
JSONConvert to support it. Is that something you would like me to do
and submit a patch for?

Also, I would like the ability to import interfaces too...I know that
sounds ridiculous, but hear me out. I actually dislike the support for
interface or abstract class deserialization in XmlSerializer or
DataContractSerializer etc where I have to specify all the types it
can be by marking up the properties with decorator attributes. I would
actually like to be able to configure a factory in the deserialization
process which will get called to create the appropriate type from a
value passed in. Thoughts??/ Ideas???

By the way, *LOVE* the design of JayRock JSON serialization. The fact
that i can read anything into a JSONObject is awesome. I am not sure
why XML deserializers can't provide the same capability. I would
actually love to use the JayRock design for my XML serialization/
deserialization also but unfortunately that can't happen...

Thank You,
Vish
> [InterfaceExportDemo.cs1K ]using System;
> using Jayrock.Json;
> using Jayrock.Json.Conversion;
>
> interface IFoo
> {
>     string Text { get; }
>
> }
>
> class Bar : IFoo
> {
>     public string Text { get { return "Bar"; } }
>
> }
>
> class Baz : IFoo
> {
>     public string Text { get { return "Baz"; } }
>
> }
>
> internal class FooExporter<T> : IExporter where T : IFoo
> {
>     public Type InputType { get { return typeof(T); } }
>
>     void IExporter.Export(ExportContext context, object value, JsonWriter writer)
>     {
>         Export(context, (T) value, writer);
>     }
>
>     public virtual void Export(ExportContext context, T value, JsonWriter writer)
>     {
>         writer.WriteStartObject();
>         writer.WriteMember("text");
>         context.Export(value.Text.ToUpperInvariant(), writer);
>         writer.WriteEndObject();
>     }
>
> }
>
> public class Program
> {
>         public static void Main()
>         {
>             var ec = new ExportContext();
>
>         ec.Register(new FooExporter<Bar>());
>         ec.Register(new FooExporter<Baz>());
>
>         ec.Export(new Bar(), new JsonTextWriter(Console.Out));
>         ec.Export(new Baz(), new JsonTextWriter(Console.Out));
>     }
>
>
>
> }- Hide quoted text -
>
> - Show quoted text -- Hide quoted text -
>
> - Show quoted text -

Atif Aziz

unread,
Mar 3, 2009, 3:46:54 PM3/3/09
to jay...@googlegroups.com
>>
real useful to be able to register interface types /abstract
class types for export
<<

Registering an exporter for abstract and base classes already works.

The problem is not the utility of exporters for interfaces, but rather how to make sure one can provide the support without introducing too much ambiguity and unnecessarily odd rules. Suppose a class called FooBar implements interfaces IFoo and IBar and you have exporters registered for both interfaces but not for FooBar. How do you then go about selecting which exporter to use? There is no ambiguity with base classes because a type can only derive from one. If you don't find an exporter for the exact run-time type being exported, you can always walk up the chain of base classes and look up a corresponding exporter (which Jayrock already does), but the same does not apply for interfaces. Also, the order of registration for interfaces would have to be significant, going from narrower to wider types. For example, an IDictionary is also an IEnumerable. If you have an exporter for both, then you'd have to make sure that the IDictionary one gets considered before IEnumerable because the former will export the object as a JSON object and the latter as a JSON array. If the IEnumerable gets picked first for a Hashtable, then you'll see it exported as an array of objects contains key and value members.

>>
I will have the flexibility to have a class that
implements N interfaces and I can create N exporters to get a
representation of the same class is various formats or serialize only
specific parts of a class.
<<

Right, and how would you go about choosing which of the N interfaces to choose from when looking up the exporter? Export is a different beast than import. During import, you specify the type in advance. With export, you rely on the run-time type, because each object already knows its type and which can be queried via System.Object.GetType.

If you have a way out of this, I'm all ears. :)

Vish

unread,
Mar 3, 2009, 10:23:04 PM3/3/09
to Jayrock
Hi Atif,

I understand the problem. And really, in my opinion, the solution to
the problem could be similar to how the Dependency Injection(DI)
containers behave in similar situations. That is, we can register
exporters with names/keys and the developer can use the name/key to
use a specific exporter or if only one is found that will be used. If
multiple compatible exporters are found then the first registered
compatible exporter will be used. Kinda like if multiple components
are registered for the same service (interface/abstract class type) in
the DI container...

Does that make any sense? Do you think that is a reasonable solution?

Thank You,
Vish

Atif Aziz

unread,
Mar 4, 2009, 3:30:03 AM3/4/09
to jay...@googlegroups.com
Vish,

> register
> exporters with names/keys
> and the developer can use the name/key to
> use a specific exporter

This is tricky and I'm afraid the approach containers take may not entirely help here. You see, what if the object you're exporting something that is not the root, where you don't have the chance to specify an exact exporter? Take the FooBar, IFoo and IBar example from my earlier post. If I am exporting an array of FooBar objects then how do we tell the array exporter to export each element using a particular, perhaps even named, exporter? The same thing goes with nested objects. Say we have Baz that contains a reference to FooBar, but we're exporting an instance of Baz. How can we tell the Baz exporter that by the time it gets to exporting its reference to FooBar object, it should use the IFoo or the IBar exporter?

> or if only one is found that will be used.

This could be done if it helps, but wouldn't it be a 10% case? I mean, it would only work where a type implements one and only one interface for which a direct exporter is available.

Any other ideas?

BTW, the way Jayrock still supports this scenario is to completely delegate the problem to you, :) assuming you know better than the library in face of ambiguity. For example, ExportContext.FindExporter and ImportContext.FindImporter is a virtual methods. This means, you can subclass Export/ImportContext and override FindExporter/Importer to support application-specific types and scenarios and delegate the rest to the base implementation. Perhaps you can experiment what would work for you there? Last night, I improved this further by allowing factories to supply context overrides:

http://code.google.com/p/jayrock/source/detail?r=752

So here's an example of what you can do:

class MyExportContext : ExportContext {
public override FindExporter(Type type) {
if (typeof(IFoo).IsAssignableFrom(type))
return new FooExporter(type);
base.FindExporter(type);
}
}

Now you can just export using your context implementation:

var context = new MyExportContext();
context.Export(new FooBar(), writer);

Alternatively, you can tell Jayrock about your context during initialization:

JsonConvert.CurrentExportContextFactory = () => new MyExportContext();

Later, export as you would normally:

JsonConvert.Export(new FooBar(), Console.Out);

The difference now is that your context will always be used implicitly.

Does this help?

Vish

unread,
Mar 4, 2009, 6:17:51 PM3/4/09
to Jayrock

Cool. Didn't realize that the FindExporter was overridable. That
definitely helps. Also I can use the windsor container to get the
component for the interface instead of the if condition in your code
snippet below.

class MyExportContext : ExportContext {
public override FindExporter(Type type) {
if (typeof(IFoo).IsAssignableFrom(type))
return new FooExporter(type);
base.FindExporter(type);
}

}

That definitely is a huge help. Thank You.

Also, going back to the problem of being able to specify named
exporters for nested types etc... Doesn't the exportContext solve that
problem. I can initialize the ExporterContext with the named exporters
I want to use and somehow have the JSONConvert use that context. Not
sure it passing in a context is supported now (not looking at the code
right now). But, anyway, do you think that there is a solution there?

Thank You,
Vish
http://www.Vishcio.us
> ...
>
> read more »

Atif Aziz

unread,
Mar 5, 2009, 3:31:13 AM3/5/09
to jay...@googlegroups.com
> Didn't realize that the FindExporter was overridable.
> That definitely helps.

Glad to hear. That's precisely why I had made it virtual and left the context classes unsealed so that they can be specialized.

> Also I can use the windsor container to get the
> component for the interface

Oh, yes, certainly! In fact, if you get around to any neat ideas and usages, it'd be great if you could share them as a wiki article on the project site.

> instead of the if condition in your code
> snippet below.

That was just a sample. I figured you'll get the idea of where all it could head from there. ;)

> That definitely is a huge help. Thank You.

Cool.

> I can initialize the ExporterContext with the named exporters

That's not the problem. If all you have is a run-time type, how do you then go about mapping it to an alternate named lookup? Believe me, I spent a lot of time around the issue of interfaces :) and after scratching the design many times over, I decided to settle down on what's good enough instead of getting it completely wrong. The two approaches are the ones we've discussed so far. One is that you create a generic exporter definition with a type parameter constrained to the interface and then register it once for each class. I agree, there is some pain here in registering for every known case and doesn't work for dynamically summoned types via run-time code generation. The second approach is that you create your context subclass where you can be in full control of the importer/exporter selection process. Perhaps you can give it a shot there, by coming up with a specialization of FindExporter that can work with names and interfaces and share it here?

> Not sure it passing in a context is supported now

JsonConvert is just a helper façade class. Some people use to find the extra step of creating the context a little too much. Normally, you fire up the context and ask it to start exporting. JsonConver just does that internally with some overload to ease the pain of dealing with various text-based input and output types.
Reply all
Reply to author
Forward
0 new messages