Customizing IntermediateSerializer, part 2

Customizing IntermediateSerializer, part 2

  • Comments 8

What can you do when serialization helper properties just aren't enough?

Bring out the big guns and write your own ContentTypeSerializer.

If you have ever written a ContentTypeWriter or ContentTypeReader, this should seem familiar. To customize the XML serialization for this test class:

    class TestClass
    {
        public int elf = 23;
    }

We create this helper:

    [ContentTypeSerializer]
    class TestClassSerializer : ContentTypeSerializer<TestClass>
    {
        protected override void Serialize(IntermediateWriter output, TestClass value, ContentSerializerAttribute format)
        {
            int hundreds = value.elf / 100;
            int tens = (value.elf / 10) % 10;
            int ones = value.elf % 10;

            output.Xml.WriteStartElement("elf");
            output.Xml.WriteString(string.Format("{0} hundreds, {1} tens, and {2} ones", hundreds, tens, ones));
            output.Xml.WriteEndElement();
        }

        protected override TestClass Deserialize(IntermediateReader input, ContentSerializerAttribute format, TestClass existingInstance)
        {
            // TODO: this part left as an exercise for the reader :-)

            return new TestClass();
        }
    }

Whenever IntermediateSerializer encounters a TestClass instance, it will now call TestClassSerializer.Serialize, producing this XML:

    <XnaContent>
      <Asset Type="TestClass">
        <elf>0 hundreds, 2 tens, and 3 ones</elf>
      </Asset>
    </XnaContent>

Neat, huh? ContentTypeSerializer gives direct access to the XmlWriter and XmlReader, and thus total flexibility to do absolutely anything you can imagine.

Best of all, I didn't have to change my TestClass to do this. That means games can have a data class shared between their content build and game code, plus a custom ContentTypeSerializer which is only referenced by the Content Pipeline build, so the custom serialization code does not have to be shipped as part of the final game.

Note, however, that writing XML serialization entirely by hand is hard work. This is not something you want to do any more than you absolutely have to!

Note 2: there is a bug in Game Studio versions 2.0 and prior, which means the custom ContentTypeSerializer will be ignored if you call IntermediateSerializer directly from a program of your own. This will only work if IntermediateSerializer is called inside the Content Pipeline build process, from an importer or processor. This is fixed in the 3.0 framework.

  • whew, this is really a good thing.Yesterday i was in the need to have something like a dynamic/flexible serializer, and searched for access to the intermidate reader and writer to allow different versions of a serializable class like

    if this is version 1

       do this;

    else

       do that

    Never thought about writing an own ContentTypeSerializer. Thx for that articles.

    Now i need to be able to call the IntermidateSerializer directly :[

    Anyway, is that what i need (classes with versions) something you would solve with the intermidate serializer? or does there any other pattern exist for my case. I`m writing a little editor. And as i know, there will be little changes in future. I would like to support older versions of data.

    Very great blog. We all learn a lot here.

  • "This is fixed in the 3.0 framework."

    Good news for my custom level editor.

  • > Anyway, is that what i need (classes with versions) something you would solve with the intermidate serializer? or does there any other pattern exist for my case.

    For the most advanced and flexible versioning options, a custom ContentTypeSerializer could indeed be a good way to go.

    For many simpler things (where an option might be deprecated, or a newer field might not be present in older files) you can do this just by specifying the Optional flag with a ContentSerializerAttribute. We used that for instance in the SpriteFontDescription type, when we added kerning support in the 2.0 framework, but still wanted to be able to deserialize older .spritefont files that did not include this information.

    Another option would be to use polymorphism, and keep all the different versions of your data layout around as separate classes (all derived from a common base type). Then your XML could specify eg. a Type="MyDataV2" attribute to indicate which specific type it was using.

  • "Then your XML could specify eg. a Type="MyDataV2" attribute to indicate which specific type it was using."

    Or Version="1.0.1" ...

  • First of all i must say your posts are great. I try to read as much of them as i can, and they help a lot - or at least expand my knowledge!

    with that being said, i have a few questions about the intermediate serializer:

    1. in your post you seem to use the .Xml property of the intermediate serializer to write and read the data. i was wondering why not use the intermediate serializer write and read functions? i tried it, it seems to do a pretty good job - less control, but still more than without the custom serializer ..

    2. this is more of a design question. so i have a class in my game project that contains gaem data, and i use the intermediate serializer (or rather would like to use it) to read the data from a file.

    the problem is pretty much on how to create it.

    for that specific data i'm using an editor (winForms applications if it matters) taht has a reference to the game library (where the actual game data classes are). here's the real problem:

    the game class doesn't contains any constructors or methods to be able to create the data - which seems fine to me as it shouldn't. the game doesn't create it as run-time, it only imports it from a file.

    so how do i create it? if i recreate those classes in another project, the output data will have different namespaces which requires me to change some things manually - which i really don't want to do.

    i can change the game data classes to be able to add and create data, but it just doesn't seem right as the game won't use it.

    here is a little sample in case my words are a litle blurry (happen sometimes :P)

    public class Skill

    {

    private String _name;

    private String _animName;

    public String Name { get { return _name; } }

    public String AnimName { get { return _animName; } }

    private Skill() { }

    }

    public class Skills

    {

    private const String SkillsAssetName = "skills";

    private static Skills _instance;

    private Dictionary<String, Skill> _skills;

    public static void Create(Game game)

    {

    _instance = game.Content.Load<Skills>(SkillsAssetName);

    }

    public static Skill GetSkill(String skillName)

    {

    return _instance._skills[skillName];

    }

    }

    i know this sample is a bit logically bad, but i couldn't think of a short one :P

    Yours, Matan.

  • Matan:

    1) Do you mean the IntermediateReader and IntermediateWriter? (there are no read and write methods directly on IntermediateSerializer).

    Sure, you could use those, but that would just give you the default serialization behavior, same as if you didn't customize anything at all. If you want to change how the data is represented in XML, you most likely want to go straight to the XML interfaces rather than just calling back into the automatic serializer.

    2) This seems like rather too involved a question to answer in blog comments! I would recommend taking it to the forums on creators.xna.com.

  • Shawn: first of all, thank you for your response.

    1) actually it has one benefit - and that is when using 3rd party or framework (xna or .net) classes, which doesn't implement any serialization or any propertie's attributes related such as ContentSerialization; which then gives you the ability to serialize the data you want on a class you can't change it code.

  • Hey Shawn,

    Somehow my ContentTypeSerializer is not being picked up by the IntermediateSerializer. It does have the ContentTypeSerializerAttribute on it. Does it have to have any extra requirements to be picked up? like being in a specific assembly, public, etc?.

    Thanks,

    Juan

Page 1 of 1 (8 items)
Leave a Comment
  • Please add 4 and 3 and type the answer here:
  • Post