Cannot serialize member <x> because it is an interface

12,810

Solution 1

By default, uses DataContractSerializer to serialize to XML, however the error message "Cannot serialize member <x> because it is an interface" is generated by XmlSerializer, so apparently you have switched to that.

You could consider switching back to DataContractSerializer as specified here, which can serialize properties of type IEnumerable<T> as long as the underlying type T can be serialized.

Alternatively, if you don't want to do that, you could modify your ProfileObject class to return proxy arrays for serialization without changing your underlying design:

public class ProfileObject
{
    public Person Person { get; set; }

    [XmlIgnore]
    public IEnumerable<Node<Language>> Language { get; set; }

    [XmlIgnore]
    public IEnumerable<Node<Country>> Country { get; set; }

    [XmlArray("Languages")]
    [XmlArrayItem("Language")]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public Node<Language>[] LanguageArray
    {
        get
        {
            if (Language == null)
                return null;
            return Language.ToArray();
        }
        set
        {
            Language = value;
        }
    }

    [XmlArray("Countries")]
    [XmlArrayItem("Country")]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public Node<Country>[] CountryArray
    {
        get
        {
            if (Country == null)
                return null;
            return Country.ToArray();
        }
        set
        {
            Country = value;
        }
    }
}

Update

XmlSerializer will only serialize properties with public get and set methods. Since Node.Data is get-only, it can't be serialized by XmlSerializer.

Since you only need to serialize the data and not the Node<TData>, and never need to deserialize, you can use linq to return transform your enumerable of nodes to an array of data for serialization as follows:

public static class NodeExtensions
{
    public static TData [] ToDataArray<TData>(this IEnumerable<Node<TData>> nodes)
    {
        if (nodes == null)
            return null;
        return nodes.Select(n => n.Data).ToArray();
    }
}

public class ProfileObject
{
    public Person Person { get; set; }

    [XmlIgnore]
    public IEnumerable<Node<Language>> Language { get; set; }

    [XmlIgnore]
    public IEnumerable<Node<Country>> Country { get; set; }

    [XmlArray("ArrayOfLanguage")]
    [XmlArrayItem("Language")]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public Language [] LanguageArray
    {
        get
        {
            return Language.ToDataArray();
        }
        set
        {
            throw new NotImplementedException();
        }
    }

    [XmlArray("ArrayOfCountry")]
    [XmlArrayItem("Country")]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public Country [] CountryArray
    {
        get
        {
            return Country.ToDataArray();
        }
        set
        {
            throw new NotImplementedException();
        }
    }
}

Solution 2

So, you shouldn't need to add any changes to the Node<T> class, it should deserialize everything fine.

So, this is my current setup:

public class Country
{
    public string Name { get; set; }
}

public class Language
{
    public string Name { get; set; }
}

public class ProfileObject
{
    public Person Person { get; set; }
    public IEnumerable<Node<Language>> Language { get; set; }
    public IEnumerable<Node<Country>> Country { get; set; }
}

public class Person
{
    public int Id { get; set; }
    public string Email { get; set; }
}

You don't define Country or Person so I've made them up, but in essence I think it's pretty close. Now I initialize my db to have something like:

(PERSON)-[:SPEAKS]->(ENGLISH)
(PERSON)-[:SPEAKS]->(GERMAN)
(PERSON)-[:CURRENT_LOCATION]->(GERMANY)

First off, running your query 'as is' (i.e. Copied from this page directly) works fine for me, in terms of serialization - can you confirm that your Language, Person, Country and ProfileObject are as you've defined either with default constructors or explicit parameterless constructors?

As to what you're expecting back - I guess you're after a ProfileObject with 2 Language objects, and only 1 Country. Now, the query you write is converted to Cypher like this:

MATCH (person:Person)
WHERE (person.Email = "THE EMAIL ADDRESS HERE")
OPTIONAL MATCH (person)-[:SPEAKS]-(language:Language)
OPTIONAL MATCH (person)-[:CURRENT_LOCATION]-(country:Country)
RETURN person AS Person, collect(language) AS Language, collect(country) AS Country

If you run this in the Neo4j manager (localhost:7474) and switch to the 'rows' view (from the graph one) you'll see you actually get returned the data in the form you're getting in the Client.

I would tweak the query in the neo4j management view until you get what you want. It might be worth not returning CollectAs and doing a LINQ .Group afterwards.

Share:
12,810

Related videos on Youtube

Shaine Fisher
Author by

Shaine Fisher

Director &amp; Senior Developer at Sticky Kiwi Ltd, owners of TAPIG (There's A Place I Go), an Android based shopping and social app.

Updated on August 31, 2022

Comments

  • Shaine Fisher
    Shaine Fisher over 1 year

    Follow on question from Error with explicit conversion when using CollectAs<>

    Code from WebMethod

    return client.Cypher
            .Match("(person:Person)")
            .Where((Person person) => person.Email == username)
            .OptionalMatch("(person)-[:SPEAKS]-(language:Language)")
            .OptionalMatch("(person)-[:CURRENT_LOCATION]-(country:Country)"
            .Return((person, language, country) => new ProfileObject
            {
                Person = person.As<Person>(),
                Language = language.CollectAs<Language>(),
                Country = country.CollectAs<Country>()
            }).Results.ToList();
    

    Code from Country Class:

    public class Language
    {
        public string Name { get; set; }
    }
    

    New code from ProfileObject Class:

    public class ProfileObject
    {
        public Person Person { get; set; }
        public IEnumerable<Node<Language>> Language { get; set; }
        public IEnumerable<Node<Country>> Country { get; set; }
    }
    

    This error only happens when I set ProfileObject to return IEnumerable>, if I return it to just

    public Country Country {get; set;} 
    

    then it works (but I obviously get duplicated Person entries for each Country object returned.

    Anyone able to show me a solution to this problem that doesn't involve me ripping all of the code out and starting again?

    Update:

    [InvalidOperationException: Neo4jClient.Node`1[Graph.Language] cannot be serialized because it does not have a parameterless constructor.]

    [InvalidOperationException: Cannot serialize member 'Graph.ProfileObject.Language' of type 'System.Collections.Generic.List1[[Neo4jClient.Node1[[Graph.Language, Graph, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Neo4jClient, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]', see inner exception for more details.] System.Xml.Serialization.StructModel.CheckSupportedMember(TypeDesc typeDesc, MemberInfo member, Type type) +5451673 System.Xml.Serialization.StructModel.CheckSupportedMember(TypeDesc typeDesc, MemberInfo member, Type type) +69 System.Xml.Serialization.StructModel.GetPropertyModel(PropertyInfo propertyInfo) +125 System.Xml.Serialization.StructModel.GetFieldModel(MemberInfo memberInfo) +89 System.Xml.Serialization.XmlReflectionImporter.InitializeStructMembers(StructMapping mapping, StructModel model, Boolean openModel, String typeName, RecursionLimiter limiter) +618 System.Xml.Serialization.XmlReflectionImporter.ImportStructLikeMapping(StructModel model, String ns, Boolean openModel, XmlAttributes a, RecursionLimiter limiter) +378 System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType, XmlAttributes a, Boolean repeats, Boolean openModel, RecursionLimiter limiter) +1799

    [InvalidOperationException: There was an error reflecting type 'Graph.ProfileObject'.] System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType, XmlAttributes a, Boolean repeats, Boolean openModel, RecursionLimiter limiter) +1917 System.Xml.Serialization.XmlReflectionImporter.CreateArrayElementsFromAttributes(ArrayMapping arrayMapping, XmlArrayItemAttributes attributes, Type arrayElementType, String arrayElementNs, RecursionLimiter limiter) +263 System.Xml.Serialization.XmlReflectionImporter.ImportArrayLikeMapping(ArrayModel model, String ns, RecursionLimiter limiter) +264 System.Xml.Serialization.XmlReflectionImporter.ImportAccessorMapping(MemberMapping accessor, FieldModel model, XmlAttributes a, String ns, Type choiceIdentifierType, Boolean rpc, Boolean openModel, RecursionLimiter limiter) +5456308 System.Xml.Serialization.XmlReflectionImporter.ImportMemberMapping(XmlReflectionMember xmlReflectionMember, String ns, XmlReflectionMember[] xmlReflectionMembers, Boolean rpc, Boolean openModel, RecursionLimiter limiter) +852 System.Xml.Serialization.XmlReflectionImporter.ImportMembersMapping(XmlReflectionMember[] xmlReflectionMembers, String ns, Boolean hasWrapperElement, Boolean rpc, Boolean openModel, RecursionLimiter limiter) +286

    [InvalidOperationException: There was an error reflecting 'MyResult'.] System.Xml.Serialization.XmlReflectionImporter.ImportMembersMapping(XmlReflectionMember[] xmlReflectionMembers, String ns, Boolean hasWrapperElement, Boolean rpc, Boolean openModel, RecursionLimiter limiter) +979 System.Xml.Serialization.XmlReflectionImporter.ImportMembersMapping(String elementName, String ns, XmlReflectionMember[] members, Boolean hasWrapperElement, Boolean rpc, Boolean openModel, XmlMappingAccess access) +133 System.Web.Services.Protocols.SoapReflector.ImportMembersMapping(XmlReflectionImporter xmlImporter, SoapReflectionImporter soapImporter, Boolean serviceDefaultIsEncoded, Boolean rpc, SoapBindingUse use, SoapParameterStyle paramStyle, String elementName, String elementNamespace, Boolean nsIsDefault, XmlReflectionMember[] members, Boolean validate, Boolean openModel, String key, Boolean writeAccess) +240 System.Web.Services.Protocols.SoapReflector.ReflectMethod(LogicalMethodInfo methodInfo, Boolean client, XmlReflectionImporter xmlImporter, SoapReflectionImporter soapImporter, String defaultNs) +2893

    [InvalidOperationException: Method ProfileServices.My can not be reflected.] System.Web.Services.Protocols.SoapReflector.ReflectMethod(LogicalMethodInfo methodInfo, Boolean client, XmlReflectionImporter xmlImporter, SoapReflectionImporter soapImporter, String defaultNs) +6173 System.Web.Services.Description.SoapProtocolReflector.ReflectMethod() +137 System.Web.Services.Description.ProtocolReflector.ReflectBinding(ReflectedBinding reflectedBinding) +1776 System.Web.Services.Description.ProtocolReflector.Reflect() +641 System.Web.Services.Description.ServiceDescriptionReflector.ReflectInternal(ProtocolReflector[] reflectors) +685 System.Web.Services.Description.ServiceDescriptionReflector.Reflect(Type type, String url) +118 System.Web.Services.Protocols.DocumentationServerType..ctor(Type type, String uri, Boolean excludeSchemeHostPortFromCachingKey) +230 System.Web.Services.Protocols.DocumentationServerProtocol.Initialize() +434 System.Web.Services.Protocols.ServerProtocolFactory.Create(Type type, HttpContext context, HttpRequest request, HttpResponse response, Boolean& abortProcessing) +122

    [InvalidOperationException: Unable to handle request.] System.Web.Services.Protocols.ServerProtocolFactory.Create(Type type, HttpContext context, HttpRequest request, HttpResponse response, Boolean& abortProcessing) +320 System.Web.Services.Protocols.WebServiceHandlerFactory.CoreGetHandler(Type type, HttpContext context, HttpRequest request, HttpResponse response) +171

    [InvalidOperationException: Failed to handle request.] System.Web.Services.Protocols.WebServiceHandlerFactory.CoreGetHandler(Type type, HttpContext context, HttpRequest request, HttpResponse response) +374 System.Web.Services.Protocols.WebServiceHandlerFactory.GetHandler(HttpContext context, String verb, String url, String filePath) +209 System.Web.Script.Services.ScriptHandlerFactory.GetHandler(HttpContext context, String requestType, String url, String pathTranslated) +48 System.Web.HttpApplication.MapHttpHandler(HttpContext context, String requestType, VirtualPath path, String pathTranslated, Boolean useAppConfig) +226 System.Web.MapHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +145 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155

  • Shaine Fisher
    Shaine Fisher almost 9 years
    Lovely answer, very detailed and probably quite accurate, sadly no help to me, when I amend my code I get the error: Neo4jClient.Node`1[Graph.Language] cannot be serialized because it does not have a parameterless constructor. As you can see Language does have a parameterless constructor. As for using DataContractSerializer, I didn't make a change away from it as far as I know. I am going to review what is happening, there is no way I shouldn't be able to format the returned data, this seems to be my fault, maybe bad design. Thank you for your help.
  • Shaine Fisher
    Shaine Fisher almost 9 years
    Judging by the error I would hazard a guess that Neo4jClient.Node is not parameterless, so in summary, I am busted. It would appear that I would need to make a constructor for Neo4jClient.Node that was parameterless, and I can't do that easily (I suppose I could download the source and make a new constructor, but for future updates this would be a terrible idea). So, unless I have an 11th hour miracle it looks like this is not really possible.
  • Shaine Fisher
    Shaine Fisher almost 9 years
    Here is the code for Neo4jClient.Node: namespace Neo4jClient { public class Node<TNode> : IGremlinQuery, IHasNodeReference { readonly TNode data; readonly NodeReference<TNode> reference; public Node(TNode data, NodeReference<TNode> reference) { if (reference == null) throw new ArgumentNullException("reference"); this.data = data; this.reference = reference; }
  • Shaine Fisher
    Shaine Fisher almost 9 years
    And just for completeness, I added a parameterless constructor to Neo4jClient.Node, which did take away the error (the expected behaviour) but the result just contains empty nodes for Language and Country (obviously because we are not passing the node to get the data from). <Languages> <Language /> <Language /> </Languages> <Countries> <Country /> <Country /> </Countries>
  • dbc
    dbc almost 9 years
    @ShaineFisher - Looking at the source code for Node<TNode> here, the XML is empty because the Data and Reference properties are get-only. 1) Do you actually need to serialize the Node<T>, or would it be sufficient to serialize the underlying T? 2) Do you need to deserialize, or is it sufficient to serialize?
  • Shaine Fisher
    Shaine Fisher almost 9 years
    I only really need the data, specifically Country.Name and Language.Name, they are being consumed by a Flexclient, so no real need at this point to deserialize.
  • Shaine Fisher
    Shaine Fisher almost 9 years
    we have the same setup, you have it right, and the data that comes back is correct, Tatham gave me some good advice to get tot his point, but as I said in the other post() I then get Unable to serialize because Country is an interface, which is why this thread started. It appears that the XMLSerializer is not liking trying to serialize Neo4jClient.Node<Country>, country isn't the issue because if I don't use IEnumberable<Node<Country>> it doesn't error. Other thread: stackoverflow.com/questions/31563378/…
  • Shaine Fisher
    Shaine Fisher almost 9 years
    The only difference between my results and yours is that I threw in 2 Countries for CURRENT_LOCATION, just for testing.
  • Shaine Fisher
    Shaine Fisher almost 9 years
    to continue from my previous comment, that is not only detailed, accurate and really well explained, it is perfect. Problem solved. I actually didn't know enough about Neo4jClient to make any real comment (apart from it is fantastic and the support is outstanding), it isn't my code, it is far more complex than I understand, and I would probably never have looked there for a solution. Thank you.
  • Charlotte Skardon
    Charlotte Skardon almost 9 years
    @ShaineFisher I would strongly consider Tatham's advice from your last question and update Neo4jClient to the Pre-release version - you don't want to be passing around Node elements, and when the next version of Neo4jClient is released it won't be returning Node<T> for CollectAs.