6

Suppose I have a following class:

public class Test {
  int x { get; set; }
  int y { get; set; }
  Vector3 coords { get; set; }
}

How can I serialize this object if I canont use [ProtoContract] and [ProtoMember(x)] attributess on Vector3 class, which comes from external assembly.

I have read How can I serialize a 3rd party type using protobuf-net or other serializers? but it is vague (for example I don't know if I can mix TypeModel and attributes approach, or how to add unknowd type member as a field to known type member if I choose to use only TypeModel approach etc.), so I need a concrete example for my situation.

For example, I declare TypeModel like this:

RuntimeTypeModel.Default.Add(typeof(Vector3), false).Add(1, "x").Add(2, "y").Add(3, "z");
RuntimeTypeModel.Default.Add(typeof(SerializableTestClass), false).Add(1, "_x").Add(2, "_y").Add(3, "_coords");

Serialization/deserialization:

if (GUILayout.Button("Serialize")) {
    SerializableTestClass testClass = new SerializableTestClass();
    testClass.changeMembers();
    RuntimeTypeModel.Default.Serialize(_serializedObject, testClass);
}

if (GUILayout.Button("Deserialize")) {
    SerializableTestClass test = (SerializableTestClass) RuntimeTypeModel.Default.Deserialize(_serializedObject, null, typeof(SerializableTestClass));
    Debug.Log("Deserialized object: " + test.ToString());
}

And when I try to serialize, I get an error:

InvalidOperationException: Duplicate field-number detected; 1 on: SerializableTestClass

UPDATE ============================

Now, I changed my code so everything looks like this: Serializable class:

[ProtoContract]
public class SerializableTestClass {
    [ProtoMember(1)]
    int _x { get; set; }
    [ProtoMember(2)]
    int _y { get; set; }
    [ProtoMember(3)]
    Vector3 _coords { get; set; }

    public SerializableTestClass() {
        Debug.Log("SerializableTestClass.ctor()");
        _x = 10;
        _y = 20;
        _coords = Vector2.one * 2;
    }

    public void changeMembers() {
        _x += -3;
        _y += 134;
        _coords *= 3;
    }

    public override string ToString() {
        return _x.ToString() + " " + _y + " " + _coords;
    }
}

Model:

_model = TypeModel.Create();
_model.Add(typeof(Vector3), false).Add(1, "x").Add(2, "y").Add(3, "z");
_model.Add(typeof(SerializableTestClass), true);

Serialization/Deserialization:

if (GUILayout.Button("Serialize")) {
    SerializableTestClass testClass = new SerializableTestClass();
    _serializedObject = new MemoryStream();
    testClass.changeMembers();
    _model.Serialize(_serializedObject, testClass);
}

if (GUILayout.Button("Deserialize")) {
    SerializableTestClass test = (SerializableTestClass) _model.Deserialize(_serializedObject, null, typeof(SerializableTestClass));
    Debug.Log("Deserialized object: " + test.ToString());
}

Output: 10 20 (2.0, 2.0, 2.0)

Should be: 7 154 (6.0, 6.0, 6.0)

Community
  • 1
  • 1
GuardianX
  • 515
  • 11
  • 29
  • possible duplicate of [How can I serialize a 3rd party type using protobuf-net or other serializers?](http://stackoverflow.com/questions/7869516/how-can-i-serialize-a-3rd-party-type-using-protobuf-net-or-other-serializers) – Rotem Sep 10 '14 at 07:00
  • Have you read this [article](http://stackoverflow.com/questions/8901287/protobuf-net-fails-deserializing-my-class)? I think this will be good example for you. – hyun Sep 10 '14 at 07:08
  • @Rotem I read it but what I need is exact example, since I don't know if I should add only unknown type to the model or should I add everything to the model and omit attributes alltogether. Documentation is not clear about it. I tried different approaches and they don't seem to work. – GuardianX Sep 10 '14 at 07:17
  • Yes, you can mix and match types; so you can use attributes on `Test` / `SerializableTestClass`, and use `RuntimeTypeModel` to configure `Vector3`. Re "duplicate field-number"... are you sure you're only using `Add` once (per field)? – Marc Gravell Sep 10 '14 at 08:52
  • @MarcGravell yes I'm sure. However, I added it to RuntimeTypeModel.Default. But when I add the class description to the type model, created via TypeModel.Create(), serialization at least works. Not how I expect it to, but at least it shows something in debug. I added a bit more clarificartion to the original post. – GuardianX Sep 10 '14 at 10:24

1 Answers1

2

This is going to sound silly, but the following is how I reproduced this accidentally: check you don't have your own ProtoContractAttribute defined locally; basically, check what happens when you put the cursor on [ProtoContract] and then either press f12, or right-click and go to show definition. What you should see is something like:

enter image description here

It is, however, possible that when resolving types, you accidentally selected "Generate class for 'ProtoContract' in (...various options...)" - this is done very easily if you don't actually have the reference at the time and just press ctrl+.,enter (the quick way of adding usings). This generates instead a file like:

using System;

internal class ProtoContractAttribute : Attribute
{
}

The important point here is that it is in the wrong namespace, so protobuf-net doesn't treat it as a relevant attribute.

So: if you're as clumsy as me, this could be the reason...


Unrelated, but if you have non-default values in the constructor, you should probably skip the constructor during deserialization; you do that via:

[ProtoContract(SkipConstructor=true)]

The following is my mockup using regular .NET and a faked Vector3; it works fine:

using ProtoBuf;
using ProtoBuf.Meta;
using System;
using System.IO;

[ProtoContract(SkipConstructor=true)]
public class SerializableTestClass
{
    [ProtoMember(1)]
    int _x { get; set; }
    [ProtoMember(2)]
    int _y { get; set; }
    [ProtoMember(3)]
    Vector3 _coords { get; set; }

    public SerializableTestClass()
    {
        _x = 10;
        _y = 20;
        _coords = Vector3.one * 2;
    }

    public void changeMembers()
    {
        _x += -3;
        _y += 134;
        _coords *= 3;
    }

    public override string ToString()
    {
        return _x.ToString() + " " + _y + " " + _coords;
    }
}

struct Vector3
{
    public int x, y, z;
    public static Vector3 one = new Vector3 { x = 1, y = 1, z = 1 };
    public static Vector3 operator *(Vector3 value, int times)
    {
        return new Vector3
        {
            x = value.x * times,
            y = value.y * times,
            z = value.z * times
        };
    }
    public override string ToString()
    {
        return string.Format("({0}, {1}, {2})", x, y, z);
    }
}
class Program
{
    static RuntimeTypeModel _model;
    static void Main()
    {
        _model = TypeModel.Create();
        _model.Add(typeof(Vector3), false).Add(1, "x").Add(2, "y").Add(3, "z");
        _model.Add(typeof(SerializableTestClass), true);

        SerializableTestClass testClass = new SerializableTestClass();
        var _serializedObject = new MemoryStream();
        testClass.changeMembers();
        Console.WriteLine("Original object: " + testClass.ToString());
        _model.Serialize(_serializedObject, testClass);

        _serializedObject.Position = 0;
        Console.WriteLine(_serializedObject.Length);
        SerializableTestClass test = (SerializableTestClass)_model.Deserialize(_serializedObject, null, typeof(SerializableTestClass));
        Console.WriteLine("Deserialized object: " + test.ToString());
    }
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900