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

Support NamingConvention option for per serialize/deserialize #121

Merged
merged 8 commits into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
push:
branches:
- "master"
pull_request:
pull_request_target:
branches:
- "master"

Expand Down
38 changes: 26 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,31 @@ documents[2]["Fatal"] // #=> "Unknown variable \"bar\""

:exclamation: By default, VYaml maps C# property names in lower camel case (e.g. `propertyName`) format to yaml keys.

If you want to customize this behaviour, use argment of `[YamlObject]` attribute.
If you want to customize this behaviour, `YamlSerializerOptions.NamingConvention` to set it.

```cs
var options = YamlSerializerOptions.Standard;
options.NamingConvention = NamingConvention.SnakeCase;

YamlSerializer.Serialize(new A { FooBar = 123 }, options); // #=> "{ foo_bar: 123 }"
```

List of possible values:
- NamingConvention.LowerCamelCase
- Like `propertyName`
- NamingConvention.UpperCamelCase:
- Like `PropertyName`
- NamingConvention.SnakeCase:
- Like `property_name`
- NamingConvention.KebabCase:
- Like `property-name`


> [!TIP]
> If you specify an option other than the default `LowerCamelCase`, there will be a slight performance degradation at runtime.

You may specify NamingConvention for each type declaration by `[YamlObject]` attribute.
In this case, no performance degradation occurs.

```csharp
[YamlObject(NamingConvention.SnakeCase)]
Expand All @@ -253,17 +277,7 @@ This serialize as:
foo_bar: 100
```

List of possible values:
- NamingConvention.LowerCamelCase
- Like `propertyName`
- NamingConvention.UpperCamelCase:
- Like `PropertyName`
- NamingConvention.SnakeCase:
- Like `property_name`
- NamingConvention.KebabCase:
- Like `property-name`

Alos, you can change the key name each members with `[YamlMember("name")]`
Also, you can change the key name each members with `[YamlMember("name")]`

```csharp
[YamlObject]
Expand Down
1 change: 0 additions & 1 deletion VYaml.Annotations/Attributes.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#nullable enable
using System;

namespace VYaml.Annotations
Expand Down
2 changes: 1 addition & 1 deletion VYaml.Annotations/VYaml.Annotations.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net8.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;netstandard2.1;net6.0;net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>9</LangVersion>
Expand Down
10 changes: 10 additions & 0 deletions VYaml.SourceGenerator.Roslyn3/VYaml.SourceGenerator.Roslyn3.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@
Exclude="**/obj/**;**/VYamlIncrementalSourceGenerator.cs;**/Shims/**" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\VYaml.Annotations\VYaml.Annotations.csproj" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\VYaml\Serialization\NamingConventionMutator.cs">
<Link>NamingConventionMutator.cs</Link>
</Compile>
</ItemGroup>

<ItemGroup>
<!-- Mainly used for Unity, Unity 2021.3 has Roslyn 3.9.0(see: Editor\Data\DotNetSdkRoslyn\ -->
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" PrivateAssets="all" />
Expand Down
1 change: 1 addition & 0 deletions VYaml.SourceGenerator/CodeWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,6 @@ public void EndBlock()
public void Clear()
{
buffer.Clear();
indentLevel = 0;
}
}
25 changes: 20 additions & 5 deletions VYaml.SourceGenerator/Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -273,13 +273,21 @@ static bool TryEmitSerializeMethod(TypeMeta typeMeta, CodeWriter codeWriter, in
codeWriter.AppendLine("emitter.BeginMapping();");
foreach (var memberMeta in memberMetas)
{
if (memberMeta.HasKeyNameAlias)
if (memberMeta.HasKeyNameAlias || typeMeta.NamingConventionByType != NamingConvention.LowerCamelCase)
{
codeWriter.AppendLine($"emitter.WriteString(\"{memberMeta.KeyName}\");");
}
else
{
codeWriter.AppendLine($"emitter.WriteString(\"{memberMeta.KeyName}\", ScalarStyle.Plain);");
using (codeWriter.BeginBlockScope($"if (context.Options.NamingConvention == global::VYaml.Annotations.NamingConvention.{memberMeta.NamingConventionByType})"))
{
codeWriter.AppendLine($"emitter.WriteScalar({memberMeta.Name}KeyUtf8Bytes);");
}
using (codeWriter.BeginBlockScope("else"))
{
codeWriter.AppendLine($"global::VYaml.Serialization.NamingConventionMutator.MutateToThreadStaticBufferUtf8({memberMeta.Name}KeyUtf8Bytes, context.Options.NamingConvention, out var mutated, out var written);");
codeWriter.AppendLine("emitter.WriteScalar(mutated.AsSpan(0, written));");
}
}
codeWriter.AppendLine($"context.Serialize(ref emitter, value.{memberMeta.Name});");
}
Expand Down Expand Up @@ -402,6 +410,13 @@ static bool TryEmitDeserializeMethod(
codeWriter.AppendLine("throw new YamlSerializerException(parser.CurrentMark, \"Custom type deserialization supports only string key\");");
}
codeWriter.AppendLine();

using (codeWriter.BeginBlockScope($"if (context.Options.NamingConvention != global::VYaml.Annotations.NamingConvention.{typeMeta.NamingConventionByType})"))
{
codeWriter.AppendLine($"global::VYaml.Serialization.NamingConventionMutator.MutateToThreadStaticBufferUtf8(key, global::VYaml.Annotations.NamingConvention.{typeMeta.NamingConventionByType}, out var mutated, out var written);");
codeWriter.AppendLine("key = mutated.AsSpan(0, written);");
}

using (codeWriter.BeginBlockScope("switch (key.Length)"))
{
var membersByNameLength = typeMeta.MemberMetas.GroupBy(x => x.KeyNameUtf8Bytes.Length);
Expand All @@ -415,8 +430,8 @@ static bool TryEmitDeserializeMethod(
using (codeWriter.BeginBlockScope($"{branching} (key.SequenceEqual({memberMeta.Name}KeyUtf8Bytes))"))
{
codeWriter.AppendLine("parser.Read(); // skip key");
codeWriter.AppendLine(
$"__{memberMeta.Name}__ = context.DeserializeWithAlias<{memberMeta.FullTypeName}>(ref parser);");
codeWriter.AppendLine($"__{memberMeta.Name}__ = context.DeserializeWithAlias<{memberMeta.FullTypeName}>(ref parser);");
codeWriter.AppendLine("continue;");
}
branching = "else if";
}
Expand Down Expand Up @@ -582,4 +597,4 @@ static bool TryGetConstructor(
constructedMembers = parameterMembers;
return !error;
}
}
}
75 changes: 0 additions & 75 deletions VYaml.SourceGenerator/KeyNameMutator.cs

This file was deleted.

10 changes: 6 additions & 4 deletions VYaml.SourceGenerator/MemberMeta.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class MemberMeta
public bool HasExplicitOrder { get; }
public bool HasKeyNameAlias { get; }
public string KeyName { get; }
public NamingConvention NamingConventionByType { get; }

public bool IsConstructorParameter { get; set; }
public bool HasExplicitDefaultValueFromConstructor { get; set; }
Expand All @@ -26,12 +27,13 @@ class MemberMeta
public byte[] KeyNameUtf8Bytes => keyNameUtf8Bytes ??= System.Text.Encoding.UTF8.GetBytes(KeyName);
byte[]? keyNameUtf8Bytes;

public MemberMeta(ISymbol symbol, ReferenceSymbols references, NamingConvention namingConvention, int sequentialOrder)
public MemberMeta(ISymbol symbol, ReferenceSymbols references, int sequentialOrder, NamingConvention namingConventionByType = default)
{
Symbol = symbol;
Name = symbol.Name;
Order = sequentialOrder;
KeyName = KeyNameMutator.Mutate(Name, namingConvention);
NamingConventionByType = namingConventionByType;
KeyName = NamingConventionMutator.Mutate(Name, NamingConventionByType);

var memberAttribute = symbol.GetAttribute(references.YamlMemberAttribute);
if (memberAttribute != null)
Expand Down Expand Up @@ -68,7 +70,7 @@ public MemberMeta(ISymbol symbol, ReferenceSymbols references, NamingConvention
}
else
{
throw new Exception("member is not field or property.");
throw new InvalidOperationException("member is not field or property.");
}
FullTypeName = MemberType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
}
Expand All @@ -83,7 +85,7 @@ public string EmitDefaultValue()
{
if (!HasExplicitDefaultValueFromConstructor)
{
return (MemberType is { IsReferenceType: true, NullableAnnotation: NullableAnnotation.Annotated })
return (MemberType is { IsReferenceType: true, NullableAnnotation: NullableAnnotation.Annotated or NullableAnnotation.None })
? $"default({FullTypeName})!"
: $"default({FullTypeName})";
}
Expand Down
Loading