Is it possible to retain the order of the object when serializing #974
-
I am using YAMLDotnet to build a devantler/dotnet-kubernetes-generator, which is a library to generate Kubernetes API resources as well as custom resources. I am building this library as I need it for a new feature (generate Kubernetes resources with When serializing objects, the order is changed such that the serialized object is in lexical order. I would like to retain the order such that for example Kubernetes Secrets look like this: ---
apiVersion: v1
kind: Secret
metadata:
name: secret
namespace: default
type: Opaque
data: {} And not this: ---
apiVersion: v1
data: {}
kind: Secret
metadata:
name: secret
namespace: default
type: Opaque Is it possible to change the serialization to keep the order of the object: var model = new V1Secret
{
ApiVersion = "v1",
Kind = "Secret",
Metadata = new V1ObjectMeta
{
Name = "secret",
NamespaceProperty = "default"
},
Type = "Opaque",
Data = new Dictionary<string, byte[]>
{
}
}; |
Beta Was this translation helpful? Give feedback.
Replies: 7 comments 9 replies
-
You may need to use a custom yaml converter. I suspect you’re using the kubernetes client library classes so putting on the yamlmember attribute isn’t a possibility. The yaml converters allow you to serialize and deserialize exactly the way you want. |
Beta Was this translation helpful? Give feedback.
-
My guess, based on your example, is that it has something to do with this line in that library that is causing you problems: .WithTypeConverter(new SystemTextJsonYamlTypeConverter(sortAlphabetically)) Our library typically serializes in the order of the properties as they are declared in the class by default. But there's no guarantees. It's really in whatever order they are returned in the reflection calls or dictionary/list. |
Beta Was this translation helpful? Give feedback.
-
Since you’re only caring about keeping those few at the top then you might be able to do something with a custom type inspector. Might be easier. The type inspector returns the properties and fields to be serialiazed and it is done in the order returned. I can probably put a basic example together if you want. |
Beta Was this translation helpful? Give feedback.
-
Got bored, here you go. This will take all objects under the using k8s.Models;
using System.Diagnostics.CodeAnalysis;
using YamlDotNet.Serialization;
var model = new V1Secret
{
ApiVersion = "v1",
Kind = "Secret",
Metadata = new V1ObjectMeta
{
Name = "secret",
NamespaceProperty = "default"
},
Type = "Opaque",
Data = new Dictionary<string, byte[]>
{
}
};
var serializer = new SerializerBuilder()
.WithTypeInspector(inner=> new KubernetesTypeInspector(inner))
.ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull)
.Build();
var yaml = serializer.Serialize(model);
Console.WriteLine(yaml);
Console.WriteLine("Proof of non kubernetes object still working");
yaml = serializer.Serialize(new NonK8sType { Name = "name", Namespace = "namespace" });
Console.WriteLine(yaml);
class NonK8sType
{
public string Name { get; set; }
public string Namespace { get; set; }
}
class KubernetesTypeInspector : ITypeInspector
{
public ITypeInspector Inner { get; }
public KubernetesTypeInspector(ITypeInspector inner)
{
Inner = inner;
}
public string GetEnumName(Type enumType, string name) => Inner.GetEnumName(enumType, name);
public string GetEnumValue(object enumValue) => Inner.GetEnumValue(enumValue);
public IEnumerable<IPropertyDescriptor> GetProperties(Type type, object? container)
{
var properties = Inner.GetProperties(type, container);
var skipped = new List<string>();
if (type.FullName?.StartsWith("k8s.Models") ?? true)
{
var apiVersion = properties.SingleOrDefault(p => p.Name == "ApiVersion");
if (apiVersion != null)
{
skipped.Add("ApiVersion");
yield return apiVersion;
}
var kind = properties.SingleOrDefault(p => p.Name == "Kind");
if (kind != null)
{
skipped.Add("Kind");
yield return kind;
}
var metadata = properties.SingleOrDefault(p => p.Name == "Metadata");
if (metadata != null)
{
skipped.Add("Metadata");
yield return metadata;
}
//special case for secrets
if (type.FullName == typeof(V1Secret).FullName)
{
// do type, then data
var typeProperty = properties.SingleOrDefault(p => p.Name == "Type");
if (typeProperty != null)
{
skipped.Add("Type");
yield return typeProperty;
}
var data = properties.SingleOrDefault(p => p.Name == "Data");
if (data != null)
{
skipped.Add("Data");
yield return data;
}
}
}
foreach (var property in properties)
{
if (!skipped.Contains(property.Name))
{
yield return property;
}
}
}
public IPropertyDescriptor GetProperty(Type type, object? container, string name, [MaybeNullWhen(true)] bool ignoreUnmatched, bool caseInsensitivePropertyMatching) => Inner.GetProperty(type, container, name, ignoreUnmatched, caseInsensitivePropertyMatching);
} Outputs:
|
Beta Was this translation helpful? Give feedback.
-
On a note on the above example, if you reverse |
Beta Was this translation helpful? Give feedback.
-
Thanks for the inspiration, I made a few changes to make it a bit more concise and generic: using System.Diagnostics.CodeAnalysis;
using YamlDotNet.Serialization;
namespace Devantler.KubernetesGenerator.Core;
/// <summary>
/// A type inspector that prioritizes properties of Kubernetes resources, so they come first, and so they are ordered according to conventions.
/// Thanks to @EdwardCooke for the initial implementation.
/// </summary>
/// <param name="inner"></param>
sealed class KubernetesTypeInspector(ITypeInspector inner) : ITypeInspector
{
public ITypeInspector Inner { get; } = inner;
public string GetEnumName(Type enumType, string name) => Inner.GetEnumName(enumType, name);
public string GetEnumValue(object enumValue) => Inner.GetEnumValue(enumValue);
public IEnumerable<IPropertyDescriptor> GetProperties(Type systemType, object? container)
{
var properties = Inner.GetProperties(systemType, container).ToList();
var skipped = new List<string>();
if (systemType.FullName?.StartsWith("k8s.Models", StringComparison.OrdinalIgnoreCase) ?? true)
{
foreach (string? propName in new[] { "apiVersion", "kind", "metadata", "type" })
{
var property = properties.SingleOrDefault(p => p.Name == propName);
if (property != null)
{
skipped.Add(propName);
yield return property;
}
}
}
foreach (var property in properties)
{
if (!skipped.Contains(property.Name))
{
yield return property;
}
}
}
public IPropertyDescriptor GetProperty(Type type, object? container, string name, [MaybeNullWhen(true)] bool ignoreUnmatched, bool caseInsensitivePropertyMatching) => Inner.GetProperty(type, container, name, ignoreUnmatched, caseInsensitivePropertyMatching);
} I will add you as a contributor on the next release of devantler/dotnet-kubernetes-generator Still need to do snapshot unit tests for about 20-30 k8s models yikes. I hope I can get it done tommorrow, but its boring work.. :-) |
Beta Was this translation helpful? Give feedback.
My guess, based on your example, is that it has something to do with this line in that library that is causing you problems:
.WithTypeConverter(new SystemTextJsonYamlTypeConverter(sortAlphabetically))
Our library typically serializes in the order of the properties as they are declared in the class by default. But there's no guarantees. It's really in whatever order they are returned in the reflection calls or dictionary/list.