Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preserve instance reference relationships by tagging instance IDs #244

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

strugglesharp
Copy link

For instance tag ObjId, realize the serialization of circular reference or multiple references.

在几乎序列化类库中, 都没有指出相同的实例引用在xml中的对应关系. 这就造成了两个问题:
In almost all serialized class libraries, the correspondence of the same instance reference in XML is not indicated. This creates two problems.

  1. A->B->C->A 会造成序列化时无限循环, 即使我们的实例是有限的.
    A->B->C->A will cause an infinite loop when serializing, even if our instances are finite.
  2. 对同一个实例多次引用后, 经过序列化和反序列化, 结果并没有还原他们的相同引用关系. 比如[A1, A1] 经过序列化和反序列化会变成[A1,A2]
    After multiple references to the same instance, serialized and deserialized, the result is that their same reference relationship is not restored. For example, [A1, A1] becomes [A1,A2] after serialization and deserialization

I tagged the instance with the ID, which solved the problem.

The test samples illustrates the functionality more clearly. Note the _ObjId=XXX.
Example1:

      public static CPack GetSampleInstance()
        {
            CBoxA boxa = new() { Name = "AAA" };
            CBoxB boxb = new() { Name = "BBB" };
            CBoxC boxc = new() { Name = "CCC" };
            boxa.Child = boxb;
            boxb.Child = boxc;
            boxc.Child = boxa;
            return new CPack { Name = "PackLoop", ItWillLoop = boxa };
        }

The result of serialization is

            <CPack xmlns:yaxlib="http://www.sinairv.com/yaxlib/">
              <Int01>666</Int01>
              <Name>PackLoop</Name>
              <ItWillLoop yaxlib:id="1000">
                <Int02>333</Int02>
                <Name>AAA</Name>
                <Child>
                  <Int01>444</Int01>
                  <Name>BBB</Name>
                  <Child>
                    <Int02>555</Int02>
                    <Name>CCC</Name>
                    <Child yaxlib:ref="1000" />
                  </Child>
                </Child>
              </ItWillLoop>
            </CPack>

Deserialization can also pass

  Assert.AreSame(got.ItWillLoop, got.ItWillLoop.Child.Child.Child);

Example2:

     public static CPack02 GetSampleInstance()
        {
            var newone = new CPack02() { BoxList = new List<CBox>() };

            for (int i = 0; i < 5; i++)
            {
                newone.BoxList.Add(new CBox
                {
                    Name = $"Node{i}",
                });
            }
            newone.BoxList[0] = newone.BoxList[2];
            newone.BoxList[4] = newone.BoxList[2];
            return newone;
        }

The result of serialization is

          <CPack02 xmlns:yaxlib="http://www.sinairv.com/yaxlib/">
              <BoxList>
                <CBox yaxlib:id="1000">
                  <Name>Node2</Name>
                </CBox>
                <CBox>
                  <Name>Node1</Name>
                </CBox>
                <CBox yaxlib:ref="1000" />
                <CBox>
                  <Name>Node3</Name>
                </CBox>
                <CBox yaxlib:ref="1000" />
              </BoxList>
            </CPack02>

Deserialization can also pass

     Assert.AreSame(got.BoxList[0], got.BoxList[2]);
     Assert.AreSame(got.BoxList[4], got.BoxList[2]);

@axunonb axunonb self-assigned this Nov 13, 2023
@axunonb axunonb marked this pull request as draft November 13, 2023 08:28
@axunonb
Copy link
Collaborator

axunonb commented Nov 13, 2023

Thanks again for the PR. Here are some thoughts after going through the code.

1. YAXSerializationOptions

1.1 Competing options

1.1.1 With this PR we have have
  • YAXSerializationOptions.ThrowUponSerializingCyclingReferences
  • YAXSerializationOptions.MarkObjId2AvertSefRef

They can be set at the same time, which is ambiguous. This is disadvantage of using [flag] enums.

1.1.2 Looking at the Json.NET implementation, they are using

a) ReferenceLoopHandling.Error
b) ReferenceLoopHandling.Ignore
c) ReferenceLoopHandling.Serialize

1.1.3 Solution for YAXLib

So we could add a property LoopHandling of type ReferenceLoopHandling to the SerializerOptions:

a) ReferenceLoopHandling.Error: This would be the same behavior as with YAXSerializationOptions.ThrowUponSerializingCyclingReferences. We mark YAXSerializationOptions.ThrowUponSerializingCyclingReferences as obsolete, but care for compatibility: In case YAXSerializationOptions.ThrowUponSerializingCyclingReferences will be set, LoopHandling will be ReferenceLoopHandling.Error and vice versa. No change to the current behavior.

b) ReferenceLoopHandling.Ignore: This is the behavor when YAXSerializationOptions.ThrowUponSerializingCyclingReferences is not set. For serialization we produce just an empty element. No change to the current behavior as shown in this unit test:

[Test]
public void
SerializingAnIndirectSelfReferringObjectMustPassWhenThrowUponSerializingCyclingReferencesOptionIsNotSet()
{
var ser = CreateSerializer<IndirectSelfReferringObject>(new SerializerOptions {
ExceptionHandlingPolicies = YAXExceptionHandlingPolicies.ThrowWarningsAndErrors,
ExceptionBehavior = YAXExceptionTypes.Error,
});
var result = ser.Serialize(IndirectSelfReferringObject.GetSampleInstanceWithLoop());
const string expectedResult =
"""
<IndirectSelfReferringObject>
<ParentDescription>I'm Parent</ParentDescription>
<Child>
<ChildDescription>I'm Child</ChildDescription>
<Parent />
</Child>
</IndirectSelfReferringObject>
""";
Assert.AreEqual(expectedResult, result);
}

c) ReferenceLoopHandling.Serialize: This would be the serialization implemented with this PR (currently marked with YAXSerializationOptions.MarkObjId2AvertSefRef).

1.2 Not implemented option

Option YAXSerializationOptions.SuppressMetadataAttributes currently has no effect when using YAXLib.Enums.YAXSerializationOptions.MarkObjId2AvertSefRef | YAXLib.Enums.YAXSerializationOptions.SuppressMetadataAttributes

YAXLib.Enums.YAXSerializationOptions.SuppressMetadataAttributes should never add YAXLib metadata attributes (e.g., 'yaxlib:realtype') to the serialized XML (even when they would be required for deserialization.) Useful when the generated XML is targeting third party systems.

Possible solution: When this option is set, we fall back to ReferenceLoopHandling.Ignore.

2. Exception YAXCannotSerializeSelfReferentialTypes

The exception will continue to be thrown for the setting ReferenceLoopHandling.Error-

3. New class Xmlhelper

3.1 Use existing definitions

There are already definitions for YaxlibNamespace and PrefixNamespace in class SerializerOptions.
Attribute names for OBJID_ELEMENT_NAME and OBJID_ELEMENT_NAME should also be implemented there.

3.2 Use existing classes

We have an existing class TypeExtensions, so extensions method rather should be placed there.

4. Miscellaneous

4.1 Method ShouldMarkObjId(this MemberWrapper member) does seem to be in use.
4.2 Replace memtype.FullName == "System.String"with more simple memtype == typeof(string)

Looking forward to your feedback on the comments.

@axunonb
Copy link
Collaborator

axunonb commented Nov 27, 2023

Hi @strugglesharp
Do the comments sound reasonable to you?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants