Serializing collections of shared resources

Serializing collections of shared resources

  • Comments 13

Tomasz emailed me a good question today:

"Very good reading about IntermediateSerializer. I was wondering however if this is possible to use it to serialize a Collection of elements which shall be serialized as shared resources. When I use [ContentSerializer(SharedResource = true)] for a Collection the collection itself is treated as a shared resource. This is a problem since I could end up with multiple "copies" of objects serialized, some of them as shared resources and some of them in the collection."

First the bad news: there is no way to do this using attributes.

But the good news is you can do it by making a custom class for your list of shared resources:

    class SharedResourceList<T> : List<T> { }

and then implementing a custom ContentTypeSerializer for this new class:

    [ContentTypeSerializer]
    class SharedResourceListSerializer<T> : ContentTypeSerializer<SharedResourceList<T>>
    {
        static ContentSerializerAttribute itemFormat = new ContentSerializerAttribute()
        {
            ElementName = "Item"
        };

protected override void Serialize(IntermediateWriter output, SharedResourceList<T> value, ContentSerializerAttribute format) { foreach (T item in value) { output.WriteSharedResource(item, itemFormat); } }
protected override SharedResourceList<T> Deserialize(IntermediateReader input, ContentSerializerAttribute format, SharedResourceList<T> existingInstance) { if (existingInstance == null) existingInstance = new SharedResourceList<T>(); while (input.MoveToElement(itemFormat.ElementName)) { input.ReadSharedResource(itemFormat, (T item) => existingInstance.Add(item)); } return existingInstance; } }

With the above code, this test:

    SharedResourceList<string> test = new SharedResourceList<string>();
test.Add("elf"); test.Add("23"); using (XmlWriter writer = XmlWriter.Create("out.xml"))
IntermediateSerializer.Serialize(writer, test, null);

produces this XML:

    <?xml version="1.0" encoding="utf-8"?>
    <XnaContent>
      <Asset Type="SharedResourceList[string]">
        <Item>#Resource1</Item>
        <Item>#Resource2</Item>
      </Asset>
      <Resources>
        <Resource ID="#Resource1" Type="string">elf</Resource>
        <Resource ID="#Resource2" Type="string">23</Resource>
      </Resources>
    </XnaContent>

2010 note: this article describes how to customize IntermediateSerializer XML. To do the same thing for binary .xnb serialization, see here.

  • Hi, are there any changes in this area? do i still need to create custom ContentTypeSerializer?

  • I was wondering, any way to create a shared resource dictionary as well?

    I started writing it without thinking too much, then realized it won't work because the fact that KeyValuePair<TKey, Tvalue> is a struct, rather than a class.

    If i'll try to write the whole KeyValuePair together, i'll get an exception saying a key cannot be found, when the intermediate serialize will try to serialize the shared resources.

    If i'll write each one separately (the key and the value, that is), I'm facing a problem when reading them. Usually I would just create the type instance, and then inside the callback methods I would put the data inside the type instance i've created. Since the type instance in our case is KeyValuePair<TKey, Tvalue> and it's a struct, the dictionary will just add a pair with empty objects.

    The only solution I could think of is to create a custom private class much like KeyValuePair, only a class. Writing will work normally. When reading, inside the callback method, i'll convert it a keyvaluepair and then add it to the dictionary.

    Is there a better way than this then?

    Matan.

  • > I was wondering, any way to create a shared resource dictionary as well?

    You need to write out the key and value separately. When you read them back in, read the two objects, then add them to the dictionary. No need to use the KeyValuePair type at all when reading, just call Dictionary<T>.Add().

  • As I said before, the idea won't work.

    Here's the code (only the Serialize/Deserialize methods):

             protected override SharedResourceDictionary<TKey, TValue> Deserialize(

               IntermediateReader input, ContentSerializerAttribute format,

               SharedResourceDictionary<TKey, TValue> existingInstance)

           {

               Int32 cnt = input.ReadObject<Int32>(Format_Count);

               if (existingInstance == null)

                   existingInstance = new SharedResourceDictionary<TKey, TValue>(cnt);

               while (cnt > 0)

               {

                   input.ReadSharedResource<TKey>(Format_Key,

                       delegate(TKey key)

                       {

                           existingInstance.Add(key, ?);

                       });

                   input.ReadSharedResource<TKey>(Format_Value,

                       delegate(TValue value)

                       {

                           existingInstance.Add(?, value);

                       });

                   --cnt;

               }

               return existingInstance;

           }

           protected override void Serialize(

               IntermediateWriter output, SharedResourceDictionary<TKey, TValue> value,

               ContentSerializerAttribute format)

           {

               output.WriteObject<Int32>(value.Count, Format_Count);

               foreach (KeyValuePair<TKey, TValue> keyValuePair in value)

               {

                   output.WriteSharedResource<TKey>(keyValuePair.Key, Format_Key);

                   output.WriteSharedResource<TValue>(keyValuePair.Value, Format_Value);

               }

           }

       }

    a bit long, if you prefer I can pastebin it next time.

    Anyway, as you can see, I wrote "?" in the deserialize method since there is nothing you can write them. You read the key and value separately, but you can only add them together.

    Matan.

  • You could add whichever key or value you get first, using a dummy placeholder object  for the other, then replace the dummy once you know both objects for real. Or you could stash the objects away in some other structure, then move them into the dictionary once both key and value are known.

  • Well, I don't see how stashing the objects in a structure would work. Both methods are callback, and the struct would just be null if you try to use it in one of the other methods, since they are just value types.

    I could use a custom private class to hold them both, and when reading the value add them both; It should work since the key is both written and read first, but I have to say I would hoped for a better solution. It doesn't look nice for sure.

  • Yes, you will have to do some work if you want to serialize dictionaries in this unusual way, but it's totally possible.

    Make a helper class that stashes a K and a V, along with flags indicating whether these values are set yet (assuming your data type doesn't have an implicit null/unset sentinel value). When reading, instantiate this helper, and bind that as the closure context for your two callback delegates for the key and value ReadSharedObject calls. In the callback methods, store the value into the helper, and set the flag, then if both flags are set, transfer both K and V into the dictionary.

  • This would be much less work if only your values and not the keys were shared, btw.

  • First of all, thanks for the information and the VERY quick responses!

    as for your last message:

    Actually I need both keys and values to be shared, but I would love to learn more; How come would it be less work if only keys were shared (I'm assuming it wouldn't be as easy if only values were shared) ?

  • > How come would it be less work if only keys were shared

    Wrong way round: shared keys are hard, shared values easy.

    To share values, your load method just inserts the key with a default value, then the ReadSharedResource callback can later assign the actual value to dictinary[key]. Just a couple lines of code and you're done.

  • Hey Shawn, I might have found a bug.

    Take a look at this xml:

    <?xml version="1.0" encoding="utf-8"?>

    <XnaContent xmlns:Design="DotEngine.Design">

     <Asset Type="Design:O">

       <col> <!-- List<String> -->

         <Item>#Resource1</Item>

         <Item>#Resource1</Item>

         <Item>#Resource1</Item>

         <Item>#Resource1</Item>

       </col>

     </Asset>

     <Resources>

       <Resource ID="#Resource1" Type="string">hola</Resource>

     </Resources>

    </XnaContent>

    How come it's deserialized as a list of four *different* instances of the same string. Is this the regular behavior?

    Once again, thanks

    Juan

  • Nevermind, I just found out that I need to do the same but with xnb serialization. You might want to clear that out at the end of this article, which btw is great.

    Thanks.

  • Hi, I have similar problem as juancampa. I have two ways of serialization - first is the xml file for editor, and second is xnb file. I created xnb file based on xml in content project. When I import xml file, everything is good and I have the same instances in my SharedList<>. But when I load class from xnb, have different instances in these lists :( Can somebody help?

Page 1 of 1 (13 items)
Leave a Comment
  • Please add 1 and 6 and type the answer here:
  • Post