DataContract serialization exception (data contract name is not expected)
Asked Answered
M

1

25

I have the following code:

[DataContract]
class TestContract {
    private String _Name;
    private Int32 _Age;

    [DataMember( Name = "Name" )]
    public String Name {
        get { return _Name; }
        set { _Name = value; }
    }

    [DataMember( Name = "Age" )]
    public Int32 Age {
        get { return _Age; }
        set { _Age = value; }
    }
}

[Serializable]
public class DNCJsonDictionary<K, V> : ISerializable {
    Dictionary<K, V> dict = new Dictionary<K, V>();

    public DNCJsonDictionary() { }

    protected DNCJsonDictionary( SerializationInfo info, StreamingContext context ) {
    }

    public void GetObjectData( SerializationInfo info, StreamingContext context ) {
        foreach( K key in dict.Keys ) {
            info.AddValue( key.ToString(), dict[ key ] );
        }
    }

    public void Add( K key, V value ) {
        dict.Add( key, value );
    }

    public V this[ K index ] {
        set { dict[ index ] = value; }
        get { return dict[ index ]; }
    }
}

public class MainClass {
    public static String Serialize( Object data ) {
        var serializer = new DataContractJsonSerializer( data.GetType() );
        var ms = new MemoryStream();
        serializer.WriteObject( ms, data );

        return Encoding.UTF8.GetString( ms.ToArray() );
    }

    public static void Main() {
        DNCJsonDictionary<String, Object> address = new DNCJsonDictionary<String, Object>();
        address[ "Street" ] = "30 Rockefeller Plaza";
        address[ "City" ] = "New York City";
        address[ "State" ] = "NY";

        TestContract test = new TestContract();
        test.Name = "CsDJ";
        test.Age = 28;

        DNCJsonDictionary<String, Object> result = new DNCJsonDictionary<String, Object>();
        result[ "foo" ] = "bar";
        result[ "Name" ] = "John Doe";
        result[ "Age" ] = 32;

        result[ "Address" ] = address;

            // ** --- THIS THROWS AN EXCEPTION!!! --- **            
        result[ "test" ] = test;

        Console.WriteLine( Serialize( result ) );

        Console.ReadLine();
    }
}

When I run, I get this exception:

Type 'Json_Dictionary_Test.TestContract' with data contract name 'TestContract:http://schemas.datacontract.org/2004/07/Json_Dictionary_Test' is not expected. Add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.

But I don't understand that! As I know, KnownTypeAttribute is used just in deserialization, and if there is inheritance, isn't it? But here is just serialization. And without the datacontract member works fine.

Any idea?


I figured out something that works! There is a parent class with a KnownTypes list, which I fill with all child classes and that will be used in serialization:

[DataContract]
[KnownType( "GetKnownTypes" )]  // for serialization
class ResultContract {
    private static List<Type> KnownTypes { get; set; }

    public static List<Type> GetKnownTypes() {
        return KnownTypes;
    }

    static ResultContract() {
        KnownTypes = new List<Type>();
        try {
            foreach( Type type in Assembly.GetExecutingAssembly().GetTypes() ) {
                if( !type.IsAbstract && type.IsSubclassOf( typeof( ResultContract ) ) ) {
                    KnownTypes.Add( type );
                }
            }
        } catch( Exception ex ) {
            Console.WriteLine( "Fatal error!" );
        }
    }
}

[DataContract]
class TestContract : *ResultContract* {
    ...
}
...
Marathi answered 2/2, 2011 at 13:48 Comment(1)
Nice solution! This is going to save me a lot of time. Instead of making all serializable classes a sub class I just check to see if they have the DataContract attribute which works better for me: if (!type.IsAbstract && type.IsDefined(typeof(DataContractAttribute), true)) { knownTypes.Add(type); }Nee
R
22

Add this line:

 [KnownType(typeof(TestContract))]

So that

[Serializable]
[KnownType(typeof(TestContract))]
public class DNCJsonDictionary<K, V> : ...

This is a known issue. That is why generics does not quite work with WCF.

But the reason is easy, WCF is supposed to create WSDL and be able to publish your contract. It is all well and good to use Generics to define your contract but WSDL needs to point to some concrete class hence you need KnownType.

Recollection answered 2/2, 2011 at 13:52 Comment(4)
Thaks, it works, but... DNCJsonDictionary is just a container, it may not know about the "KnownTypes", it's not his job. And if I use List instead of my "dictionary", it works.Marathi
If it has been exposed in a contract then it should know about KnownTypes since it will tell the framework how to serialize. I am not defending WCF, I have not designed it! In fact I do not like it: #3711135Recollection
Okay, but my question is still that: for example List&gt;T&lt; is also a generic type, and i thing, it hasn't got an [KnownType(typeof(TestContract))] attribute. But it works fine: List<TestContract> ltc = new List<TestContract>(); ltc.Add( test ); Console.WriteLine( Serialize( ltc ) ); Why? How?Marathi
List<TestContract>() Works fine since you are declaring TestContract on the List<TestContract>() itself, so there is no unknown generics involved. i.e. there is problem if you try serializing List<T> but not List<TestContract>().Recollection

© 2022 - 2024 — McMap. All rights reserved.