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

How to set custom Anchor names? #939

Closed
Schrolli91 opened this issue Jul 11, 2024 · 6 comments
Closed

How to set custom Anchor names? #939

Schrolli91 opened this issue Jul 11, 2024 · 6 comments

Comments

@Schrolli91
Copy link

Schrolli91 commented Jul 11, 2024

Is there any possibility, to step into the serialization process and modify the rules,
how anchor tags in the YAML are named? Let me show a little Example.

Maybe with a naming rule, which iterate per "dataclass" type.

What I want:

- Peers:
  - Nic: *nic_1
    Port: 1
    Device: *device_1
  - Nic: *nic_2
    Port: 1
    Device: *device_1    

What I actually get:

- Peers:
  - Nic: *o0
    Port: 1
    Device: *o1
  - Nic: *o2
    Port: 1
    Device: *o1
@EdwardCooke
Copy link
Collaborator

You could keep track of different counters per object type in a custom alias provider. That should give you what you want.

@Schrolli91
Copy link
Author

Schrolli91 commented Jul 11, 2024

Are there any docs or examples, how to implement/use such an custom Alias provider and attach it to the Serializer?

I have found some other threads with an equivalent idea/problem, but I'm not to came to a conclusion myself, because there're only hints but no example how to implement such a mechanism.

@EdwardCooke
Copy link
Collaborator

When I get to stable internet (next week) I’ll put together an example for you.

@EdwardCooke
Copy link
Collaborator

Ok, here you go.

using System.Collections.Concurrent;
using YamlDotNet.Core;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.ObjectGraphVisitors;

var serializer = new SerializerBuilder()
    .WithoutPreProcessingPhaseObjectGraphVisitor<YamlDotNet.Serialization.ObjectGraphVisitors.AnchorAssigner>()
    .WithPreProcessingPhaseObjectGraphVisitor(new AnchorAssigner(Array.Empty<IYamlTypeConverter>()))
    .WithoutEmissionPhaseObjectGraphVisitor<AnchorAssigningObjectGraphVisitor>()
    .WithEmissionPhaseObjectGraphVisitor((EmissionPhaseObjectGraphVisitorArgs args) => new AnchorAssigningObjectGraphVisitor(args.InnerVisitor, args.EventEmitter, args.GetPreProcessingPhaseObjectGraphVisitor<AnchorAssigner>()))
    .Build();
var o = new Outer();

AddInner(new Inner1 { Data = "test1" }, x => o.A.Add(x));
AddInner(new Inner1 { Data = "test2" }, x => o.A.Add(x));
AddInner(new Inner1 { Data = "test3" }, x => o.A.Add(x));
AddInner(new Inner1 { Data = "test4" }, x => o.A.Add(x));
AddInner(new Inner1 { Data = "test5" }, x => o.A.Add(x));
AddInner(new Inner2 { Data = "test1" }, x => o.B.Add(x));
AddInner(new Inner2 { Data = "test2" }, x => o.B.Add(x));
AddInner(new Inner2 { Data = "test3" }, x => o.B.Add(x));
AddInner(new Inner2 { Data = "test4" }, x => o.B.Add(x));
AddInner(new Inner2 { Data = "test5" }, x => o.B.Add(x));
AddInner(new Inner3 { Data = "test1" }, x => o.C.Add(x));
AddInner(new Inner3 { Data = "test2" }, x => o.C.Add(x));
AddInner(new Inner3 { Data = "test3" }, x => o.C.Add(x));
AddInner(new Inner3 { Data = "test4" }, x => o.C.Add(x));
AddInner(new Inner3 { Data = "test5" }, x => o.C.Add(x));

Console.WriteLine(serializer.Serialize(o));

void AddInner<TType>(TType inner, Action<TType> adder)
{
    for (var i = 0; i < 10; i++)
    {
        adder(inner);
    }
}

public class Outer
{
    public List<Inner1> A { get; set; } = new List<Inner1>();
    public List<Inner2> B { get; set; } = new List<Inner2>();
    public List<Inner3> C { get; set; } = new List<Inner3>();
}

public class Inner1
{
    public string Data { get; set; }
}

public class Inner2
{
    public string Data { get; set; }
}
public class Inner3
{
    public string Data { get; set; }
}

public class AnchorAssigner : PreProcessingPhaseObjectGraphVisitorSkeleton, IAliasProvider
{
    private class AnchorAssignment
    {
        public AnchorName Anchor;
    }

    private readonly ConcurrentDictionary<Type, IDictionary<object, AnchorAssignment>> typeAssignments = new ConcurrentDictionary<Type, IDictionary<object, AnchorAssignment>>();

    public AnchorAssigner(IEnumerable<IYamlTypeConverter> typeConverters)
        : base(typeConverters)
    {
    }

    protected override bool Enter(IObjectDescriptor value, ObjectSerializer serializers)
    {
        if (value.Value != null)
        {
            var type = value.Value.GetType();

            var assignments = typeAssignments.GetOrAdd(type, (_) => new ConcurrentDictionary<object, AnchorAssignment>());

            if (assignments.TryGetValue(value.Value, out var assignment))
            {
                if (assignment.Anchor.IsEmpty)
                {
                    assignment.Anchor = new AnchorName($"{type.Name}-{assignments.Count}");
                }
                return false;
            }
        }

        return true;
    }

    protected override bool EnterMapping(IObjectDescriptor key, IObjectDescriptor value, ObjectSerializer serializers)
    {
        return true;
    }

    protected override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, ObjectSerializer serializers)
    {
        return true;
    }

    protected override void VisitScalar(IObjectDescriptor scalar, ObjectSerializer serializers)
    {
        // Do not assign anchors to scalars
    }

    protected override void VisitMappingStart(IObjectDescriptor mapping, Type keyType, Type valueType, ObjectSerializer serializers)
    {
        VisitObject(mapping);
    }

    protected override void VisitMappingEnd(IObjectDescriptor mapping, ObjectSerializer serializers) { }

    protected override void VisitSequenceStart(IObjectDescriptor sequence, Type elementType, ObjectSerializer serializers)
    {
        VisitObject(sequence);
    }

    protected override void VisitSequenceEnd(IObjectDescriptor sequence, ObjectSerializer serializers) { }

    private void VisitObject(IObjectDescriptor value)
    {
        if (value.Value != null)
        {
            var assignments = typeAssignments.GetOrAdd(value.Value.GetType(), (_) => new Dictionary<object, AnchorAssignment>());
            assignments.Add(value.Value, new AnchorAssignment());
        }
    }

    AnchorName IAliasProvider.GetAlias(object target)
    {
        var assignments = typeAssignments.GetOrAdd(target.GetType(), (_) => new Dictionary<object, AnchorAssignment>());

        if (target != null && assignments.TryGetValue(target, out var assignment))
        {
            return assignment.Anchor;
        }
        return AnchorName.Empty;
    }
}

Results in

A:
- &Inner1-1
  Data: test1
- *Inner1-1
- *Inner1-1
- *Inner1-1
- *Inner1-1
- *Inner1-1
- *Inner1-1
- *Inner1-1
- *Inner1-1
- *Inner1-1
- &Inner1-2
  Data: test2
- *Inner1-2
- *Inner1-2
- *Inner1-2
- *Inner1-2
- *Inner1-2
- *Inner1-2
- *Inner1-2
- *Inner1-2
- *Inner1-2
- &Inner1-3
  Data: test3
- *Inner1-3
- *Inner1-3
- *Inner1-3
- *Inner1-3
- *Inner1-3
- *Inner1-3
- *Inner1-3
- *Inner1-3
- *Inner1-3
- &Inner1-4
  Data: test4
- *Inner1-4
- *Inner1-4
- *Inner1-4
- *Inner1-4
- *Inner1-4
- *Inner1-4
- *Inner1-4
- *Inner1-4
- *Inner1-4
- &Inner1-5
  Data: test5
- *Inner1-5
- *Inner1-5
- *Inner1-5
- *Inner1-5
- *Inner1-5
- *Inner1-5
- *Inner1-5
- *Inner1-5
- *Inner1-5
B:
- &Inner2-1
  Data: test1
- *Inner2-1
- *Inner2-1
- *Inner2-1
- *Inner2-1
- *Inner2-1
- *Inner2-1
- *Inner2-1
- *Inner2-1
- *Inner2-1
- &Inner2-2
  Data: test2
- *Inner2-2
- *Inner2-2
- *Inner2-2
- *Inner2-2
- *Inner2-2
- *Inner2-2
- *Inner2-2
- *Inner2-2
- *Inner2-2
- &Inner2-3
  Data: test3
- *Inner2-3
- *Inner2-3
- *Inner2-3
- *Inner2-3
- *Inner2-3
- *Inner2-3
- *Inner2-3
- *Inner2-3
- *Inner2-3
- &Inner2-4
  Data: test4
- *Inner2-4
- *Inner2-4
- *Inner2-4
- *Inner2-4
- *Inner2-4
- *Inner2-4
- *Inner2-4
- *Inner2-4
- *Inner2-4
- &Inner2-5
  Data: test5
- *Inner2-5
- *Inner2-5
- *Inner2-5
- *Inner2-5
- *Inner2-5
- *Inner2-5
- *Inner2-5
- *Inner2-5
- *Inner2-5
C:
- &Inner3-1
  Data: test1
- *Inner3-1
- *Inner3-1
- *Inner3-1
- *Inner3-1
- *Inner3-1
- *Inner3-1
- *Inner3-1
- *Inner3-1
- *Inner3-1
- &Inner3-2
  Data: test2
- *Inner3-2
- *Inner3-2
- *Inner3-2
- *Inner3-2
- *Inner3-2
- *Inner3-2
- *Inner3-2
- *Inner3-2
- *Inner3-2
- &Inner3-3
  Data: test3
- *Inner3-3
- *Inner3-3
- *Inner3-3
- *Inner3-3
- *Inner3-3
- *Inner3-3
- *Inner3-3
- *Inner3-3
- *Inner3-3
- &Inner3-4
  Data: test4
- *Inner3-4
- *Inner3-4
- *Inner3-4
- *Inner3-4
- *Inner3-4
- *Inner3-4
- *Inner3-4
- *Inner3-4
- *Inner3-4
- &Inner3-5
  Data: test5
- *Inner3-5
- *Inner3-5
- *Inner3-5
- *Inner3-5
- *Inner3-5
- *Inner3-5
- *Inner3-5
- *Inner3-5
- *Inner3-5

@Schrolli91
Copy link
Author

Works like a charme - Thank you.
I'll dive into the code and try to understand, how this works in detail.
Maybe a come back with some little questions, but from my point of view, this Issue can be closed for now.

@Schrolli91
Copy link
Author

Schrolli91 commented Jul 31, 2024

@EdwardCooke

After some more work on our project, we have realised, that the code above does not work correctly.
But we haven't found a solution right now.

The problem is, that all anchor names are the same PER TYPE, set with the maximum count of this type.

Here a short example - input:

    Interfaces:
      - &nic_1_1
        NicType: i210
        PciBusAddress: 0000:01:00.0

      - &nic_1_2
        NicType: i210
        PciBusAddress: 0000:02:00.0

      - &nic_1_3
        NicType: i210
        PciBusAddress: 0000:03:00.0

      - &nic_1_4
        NicType: i210
        PciBusAddress: 0000:04:00.0
      
      - &nic_1_5
        NicType: i210
        PciBusAddress: 0000:05:00.0
      
      - &nic_1_6
        NicType: i210
        PciBusAddress: 0000:06:00.0

output

 Interfaces:
  - &NIC-30
    NicType: i210
    PciBusAddress: 0000:01:00.0
  - *NIC-30
  - *NIC-30
  - *NIC-30
  - *NIC-30
  - *NIC-30

If we will deactivate the custom anchor assigner, we got out different objects with different anchor names, as expected. So I don't think there is a problem in our data structure at all.

 Interfaces:
  - &o0
    NicType: i210
    PciBusAddress: 0000:01:00.0
  - &o2
    NicType: i210
    PciBusAddress: 0000:02:00.0
  - &o3
    NicType: i210
    PciBusAddress: 0000:03:00.0
  - &o4
    NicType: i210
    PciBusAddress: 0000:04:00.0
  - &o5
    NicType: i210
    PciBusAddress: 0000:05:00.0
  - &o6
    NicType: i210
    PciBusAddress: 0000:06:00.0

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

No branches or pull requests

2 participants