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

[WiP] Implement deserializing to specific type #142

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
src/obj
src/bin
src/*/obj
src/*/bin
21 changes: 15 additions & 6 deletions build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,22 @@

$here = Split-Path -Parent $MyInvocation.MyCommand.Path

dotnet build --configuration Release $here/src/
dotnet build --configuration Release $here/src/PowerShellYamlSerialization
dotnet build --configuration Release $here/src/PowerShellYamlTypes

$destinations = @("netstandard2.1", "net47")
function Copy-Assemblies {
param(
$module
)

foreach ($item in $destinations) {
$src = Join-Path $here "src" "bin" "Release" $item "PowerShellYamlSerializer.dll"
$dst = Join-Path $here "lib" $item "PowerShellYamlSerializer.dll"
$destinations = @("netstandard2.1", "net47")
foreach ($item in $destinations) {
$src = Join-Path $here "src" $module "bin" "Release" $item "$module.dll"
$dst = Join-Path $here "lib" $item "$module.dll"

Copy-Item -Force $src $dst
Copy-Item -Force $src $dst
}
}

Copy-Assemblies -module "PowerShellYamlSerialization"
Copy-Assemblies -module "PowerShellYamlTypes"
Binary file added lib/net47/PowerShellYamlSerialization.dll
Binary file not shown.
Binary file added lib/net47/PowerShellYamlTypes.dll
Binary file not shown.
Binary file not shown.
Binary file removed lib/netstandard2.1/PowerShellYamlSerializer.dll
Binary file not shown.
Binary file added lib/netstandard2.1/PowerShellYamlTypes.dll
Binary file not shown.
2 changes: 1 addition & 1 deletion powershell-yaml.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Description = 'Powershell module for serializing and deserializing YAML'
PowerShellVersion = '5.0'

# Functions to export from this module
FunctionsToExport = "ConvertTo-Yaml","ConvertFrom-Yaml"
FunctionsToExport = "ConvertTo-Yaml","ConvertFrom-Yaml","ConvertFrom-YamlToClass"

AliasesToExport = "cfy","cty"
}
174 changes: 135 additions & 39 deletions powershell-yaml.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,28 @@ function Invoke-LoadFile {
)

$global:powershellYamlDotNetAssemblyPath = Join-Path $assemblyPath "YamlDotNet.dll"
$serializerAssemblyPath = Join-Path $assemblyPath "PowerShellYamlSerializer.dll"
$serializerAssemblyPath = Join-Path $assemblyPath "PowerShellYamlSerialization.dll"
$yamlAssembly = [Reflection.Assembly]::LoadFile($powershellYamlDotNetAssemblyPath)
$serializerAssembly = [Reflection.Assembly]::LoadFile($serializerAssemblyPath)
$yamlTypes = Join-Path $assemblyPath "PowerShellYamlTypes.dll"
[Reflection.Assembly]::LoadFrom($yamlTypes) | Out-Null

if ($PSVersionTable['PSEdition'] -eq 'Core') {
# Register the AssemblyResolve event to load dependencies manually. This seems to be needed only on
# PowerShell Core.
[System.AppDomain]::CurrentDomain.add_AssemblyResolve({
param ($sender, $e)
$pth = $powershellYamlDotNetAssemblyPath
# Load YamlDotNet if it's requested by PowerShellYamlSerializer. Ignore other requests as they might
# Load YamlDotNet if it's requested by PowerShellYamlSerialization. Ignore other requests as they might
# originate from other assemblies that are not part of this module and which might have different
# versions of the module that they need to load.
if ($e.Name -like "*YamlDotNet*" -and $e.RequestingAssembly -like "*PowerShellYamlSerializer*" ) {
if ($e.Name -like "*YamlDotNet*" -and $e.RequestingAssembly -like "*PowerShellYamlSerialization*" ) {
return [System.Reflection.Assembly]::LoadFile($powershellYamlDotNetAssemblyPath)
}

return $null
})
# Load the StringQuotingEmitter from PowerShellYamlSerializer to force the resolver handler to fire once.
# Load the StringQuotingEmitter from PowerShellYamlSerialization to force the resolver handler to fire once.
# This will load the YamlDotNet assembly and expand the global variable $powershellYamlDotNetAssemblyPath.
# We then remove it to avoid polluting the global scope.
# This is an ugly hack I am not happy with.
Expand Down Expand Up @@ -344,6 +346,132 @@ function Convert-PSObjectToGenericObject {
return $data
}

function Get-Serializer {
Param(
[Parameter(Mandatory=$true)][SerializationOptions]$Options
)

$builder = $yamlDotNetAssembly.GetType("YamlDotNet.Serialization.SerializerBuilder")::new()

if ($Options.HasFlag([SerializationOptions]::Roundtrip)) {
$builder = $builder.EnsureRoundtrip()
}
if ($Options.HasFlag([SerializationOptions]::DisableAliases)) {
$builder = $builder.DisableAliases()
}
if ($Options.HasFlag([SerializationOptions]::EmitDefaults)) {
$builder = $builder.EmitDefaults()
}
if ($Options.HasFlag([SerializationOptions]::JsonCompatible)) {
$builder = $builder.JsonCompatible()
}
if ($Options.HasFlag([SerializationOptions]::DefaultToStaticType)) {
$resolver = $yamlDotNetAssembly.GetType("YamlDotNet.Serialization.TypeResolvers.StaticTypeResolver")::new()
$builder = $builder.WithTypeResolver($resolver)
}
if ($Options.HasFlag([SerializationOptions]::WithIndentedSequences)) {
$builder = $builder.WithIndentedSequences()
}

$omitNull = $Options.HasFlag([SerializationOptions]::OmitNullValues)

$stringQuoted = $stringQuotedAssembly.GetType("StringQuotingEmitter")
$util = $stringQuotedAssembly.GetType("BuilderUtils")
$builder = $util::BuildSerializer($builder, $omitNull)

return $builder.Build()
}

function Resolve-AttributeOverrides {
Param(
$deserializerBuilder,
[System.Type]$theType
)

foreach ($attr in $theType.GetProperties()) {
$custom = $attr.GetCustomAttributes($true)
if ($custom -eq $null) {
continue
}
$shouldRecurse = $false
foreach ($customAttr in $custom) {
switch($customAttr.TypeId.Name) {
"PowerShellYamlPropertyAliasAttribute" {
if ($customAttr.YamlName -eq "") {
break
}
$alias = $yamlDotNetAssembly.GetType("YamlDotNet.Serialization.YamlMemberAttribute")::new()
$alias.Alias = $custom.YamlName
$deserializerBuilder = $deserializerBuilder.WithAttributeOverride($theType, $attr.Name, $alias)
break
}
"PowerShellYamlSerializable" {
$shouldRecurse = $customAttr.ShouldRecurse
break
}
}
}
if ($shouldRecurse -eq $true) {
$nestedType = $null
if ($attr.PropertyType -eq [string]) {
$nestedType = [string]
} else {
$instance = [System.Activator]::CreateInstance($attr.PropertyType)
$nestedType = $instance.GetType()
}
return (Resolve-AttributeOverrides -deserializer $deserializerBuilder -theType $nestedType)
}
}
return $deserializerBuilder
}


function Get-DeserializerBuilder {
$deserializerBuilder = $yamlDotNetAssembly.GetType("YamlDotNet.Serialization.DeserializerBuilder")::new()
$stringQuoted = $stringQuotedAssembly.GetType("BuilderUtils")
$deserializerBuilder = $stringQuoted::BuildDeserializer($deserializerBuilder)
return $deserializerBuilder
}

function ConvertFrom-YamlToClass {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[string]$Yaml,
[System.Type]$Type
)

BEGIN {
$yamlArray = @()
}

PROCESS {
if($Yaml -is [string]) {
$yamlArray += $Yaml
}
}

END {
if($yamlArray.Count -eq 0) {
return
}
$deserializerBuilder = Get-DeserializerBuilder
$deserializerBuilder = Resolve-AttributeOverrides -deserializerBuilder $deserializerBuilder -theType $Type
$deserializer = $deserializerBuilder.Build()

if ($yamlArray.Count -eq 1) {
$deserialized = $deserializer.Deserialize($yamlArray[0], $Type)
return $deserialized
}

$ret = @()
foreach($i in $yamlArray) {
$ret += $deserializer.Deserialize($i, $Type)
}
return $ret
}
}

function ConvertFrom-Yaml {
[CmdletBinding()]
Param(
Expand Down Expand Up @@ -385,41 +513,6 @@ function ConvertFrom-Yaml {
}
}

function Get-Serializer {
Param(
[Parameter(Mandatory=$true)][SerializationOptions]$Options
)

$builder = $yamlDotNetAssembly.GetType("YamlDotNet.Serialization.SerializerBuilder")::new()

if ($Options.HasFlag([SerializationOptions]::Roundtrip)) {
$builder = $builder.EnsureRoundtrip()
}
if ($Options.HasFlag([SerializationOptions]::DisableAliases)) {
$builder = $builder.DisableAliases()
}
if ($Options.HasFlag([SerializationOptions]::EmitDefaults)) {
$builder = $builder.EmitDefaults()
}
if ($Options.HasFlag([SerializationOptions]::JsonCompatible)) {
$builder = $builder.JsonCompatible()
}
if ($Options.HasFlag([SerializationOptions]::DefaultToStaticType)) {
$resolver = $yamlDotNetAssembly.GetType("YamlDotNet.Serialization.TypeResolvers.StaticTypeResolver")::new()
$builder = $builder.WithTypeResolver($resolver)
}
if ($Options.HasFlag([SerializationOptions]::WithIndentedSequences)) {
$builder = $builder.WithIndentedSequences()
}

$omitNull = $Options.HasFlag([SerializationOptions]::OmitNullValues)

$stringQuoted = $stringQuotedAssembly.GetType("StringQuotingEmitter")
$builder = $stringQuoted::Add($builder, $omitNull)

return $builder.Build()
}

function ConvertTo-Yaml {
[CmdletBinding(DefaultParameterSetName = 'NoOptions')]
Param(
Expand Down Expand Up @@ -493,7 +586,10 @@ function ConvertTo-Yaml {
}
}


New-Alias -Name cfy -Value ConvertFrom-Yaml
New-Alias -Name cty -Value ConvertTo-Yaml

Export-ModuleMember -Function ConvertFrom-Yaml,ConvertTo-Yaml -Alias cfy,cty
Export-ModuleMember -Function ConvertFrom-YamlToClass

Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,16 @@ public bool Accepts(Type type) {

public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
// We don't really need to do any custom deserialization.
var deserializedObject = rootDeserializer(typeof(IDictionary<string, object>)) as IDictionary;
return deserializedObject;
var psObject = new PSObject();
parser.Consume<MappingStart>();

while (parser.TryConsume<Scalar>(out var scalar)) {
var key = scalar.Value;
var value = rootDeserializer(typeof(object));
psObject.Properties.Add(new PSNoteProperty(key, value));
}
parser.Consume<MappingEnd>();
return psObject;
}

public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer serializer) {
Expand Down Expand Up @@ -111,14 +118,16 @@ public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter) {
eventInfo.Style = ScalarStyle.DoubleQuoted;
} else if (val.IndexOf('\n') > -1) {
eventInfo.Style = ScalarStyle.Literal;
}
}
break;
}

base.Emit(eventInfo, emitter);
}
// objectGraphVisitor, w => w.OnTop()
public static SerializerBuilder Add(SerializerBuilder builder, bool omitNullValues = false) {
}

class BuilderUtils {
public static SerializerBuilder BuildSerializer(SerializerBuilder builder, bool omitNullValues = false) {
builder = builder
.WithEventEmitter(next => new StringQuotingEmitter(next))
.WithTypeConverter(new BigIntegerTypeConverter())
Expand All @@ -129,4 +138,12 @@ public static SerializerBuilder Add(SerializerBuilder builder, bool omitNullValu
}
return builder;
}
}

public static DeserializerBuilder BuildDeserializer(DeserializerBuilder builder) {
builder = builder
.WithTypeConverter(new BigIntegerTypeConverter())
.WithTypeConverter(new PSObjectTypeConverter(false));
return builder;
}

}
7 changes: 7 additions & 0 deletions src/PowerShellYamlTypes/PowerShellYamlTypes.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.1;net47</TargetFrameworks>
</PropertyGroup>

</Project>
24 changes: 24 additions & 0 deletions src/PowerShellYamlTypes/Types.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public class PowerShellYamlSerializable : Attribute
{
public bool ShouldRecurse { get; }

public PowerShellYamlSerializable(bool shouldRecurse)
{
ShouldRecurse = shouldRecurse;
}
}


[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public class PowerShellYamlPropertyAliasAttribute : Attribute
{
public string YamlName { get; }

public PowerShellYamlPropertyAliasAttribute(string yamlName)
{
YamlName = yamlName;
}
}
Loading