powershell 将“unknown”类型的“unknown”属性设置为“unknown”值的有效方法?

1qczuiv0  于 2023-06-29  发布在  Shell
关注(0)|答案(2)|浏览(155)

我试图创建一个自动化的方法来设置对象的属性,当属性名称,类型和值是可变的。
示例:
我有一个WinForms窗体([System.Windows.Forms.Form]),我想设置SizeTopMostDock属性。我通过JSON获取这些值:

"properties": [
    {
        "name": "Size",
        "type": "System.Drawing.Size",
        "value": {
            "width": 500,
            "height": 500
        }
    },
    {
        "name": "TopMost",
        "type": "System.Boolean",
        "value": true
    },
    {
        "name": "Dock",
        "type": "System.Windows.Forms.DockStyle",
        "value": "Right"
    }
]

问题是,由于这些属性中的每一个都是不同的类型,所以它们都必须以不同的方式设置:

$form.Size = [System.Drawing.Size]::new(500, 500)
$form.TopMost = $true
$form.Dock = [System.Windows.Forms.DockStyle]::Right

现在我使用switch语句:

switch -regex ($property.type){
    "^System\.Drawing\.Size$" {
        $form.$($property.name) = [system.drawing.size]::new($property.value.width, $property.value.height)
    }
    "^System\.Windows\.Forms\.DockStyle)$" {
        $type = [type]$($property.type)
        $form.$($property.name) = $type::$($property.value)
    }
    "^System\.(String|Boolean)$" {
        $form.$($property.name) = $property.value
    }
    Default {
        $type = [type]$($property.type)
        $form.$($property.name) = $type::new($($property.value))
    }
}

我使用的Default case不适用于很多类型,所以每当我遇到我想使用的新类型时,我必须更新switch语句。例如,System.Windows.Forms.FlowDirection是一个类似于DockStyle的枚举,所以我必须更新case语句:

"^System\.Windows\.Forms\.(DockStyle|FlowDirection))$" {
    $type = [type]$($property.type)
    $form.$($property.name) = $type::$($property.value)
}

有没有一个单一的、统一的方法,无论是什么类型的方法都能起作用?

beq87vna

beq87vna1#

根据评论,没有防弹的内置机制来从你的json格式中脱离。这里有一个不完整的方法,虽然它概括了一些常见类型(枚举,值类型,满足某些约束的构造函数类)的处理......
首先,设置一个测试上下文(注意我已经改变了表单的大小,所以宽度和高度是不同的,这样我们就可以证明它们的设置是正确的):

$ErrorActionPreference = "Stop";
Set-StrictMode -Version "Latest";

$properties = @"
{
  "properties": [
    {
      "name": "Size",
      "type": "System.Drawing.Size",
      "value": {
        "width": 1024,
        "height": 768
      }
    },
    {
      "name": "TopMost",
      "type": "System.Boolean",
      "value": true
    },
    {
      "name": "Dock",
      "type": "System.Windows.Forms.DockStyle",
      "value": "Right"
    }
  ]
}
"@ | ConvertFrom-Json;

Add-Type -Assembly "System.Windows.Forms";
$form = new-object System.Windows.Forms.Form;

然后我们可以使用反射来检查JSON中每个项目的表单属性。
根据表单属性的类型,我们可以做一些一般化的处理,这些处理将 * 在一些限制下 * 工作-例如,对于对象属性,* 必须 * 有一个公共构造函数,其参数名称与json中的字段匹配。如果对于给定的类型不是这样,你仍然需要为该类型添加一个特殊的处理程序。

foreach( $property in $properties.properties )
{
    write-host $property.name;

    # needs error handling!
    $propertyType = $form.GetType().GetProperty($property.Name).PropertyType;

    # handle simple types
    if( $propertyType -in @( [bool], [string], [int32], [int64] ) )
    {
        write-host "    simple type";
        write-host "    $($propertyType.FullName)";
        write-host "    '$($property.value)'";
        $form.($property.Name) = $property.value;
        continue;
    }

    # is the form property an enum?
    if( $propertyType.IsEnum )
    {
        # powershell will automatically convert a string to the appropriate enum type
        write-host "    enum";
        write-host "    $($propertyType.FullName)";
        write-host "    '$($property.value)'";
        $form.($property.Name) = $property.value;
        continue;
    }

    # can we create an object using a constructor?
    if( $property.value -is [System.Management.Automation.PSCustomObject] )
    {
        write-host "    constructor";
        write-host "    $($propertyType.FullName)";
        # can we find an appropriate constructor for the type?
        $valueNames = @( $property.value.psobject.Properties.Name );
        $constructors = @(
            $propertyType.GetConstructors() `
                | where-object {
                    $params = $_.GetParameters();
                    # not the right constructor if it's got a different number of parameters
                    if( -not ($params.Length -eq $valueNames.Length ) )
                    {
                        return $false;
                    }
                    # are there any constructor parameters
                    # that don't appear in the json?
                    $missing = $params | where-object {
                        $valueNames -notcontains $_.Name
                    }
                    return ($null -eq $missing)
                }
        );
        if( $constructors.Length -ne 1 )
        {
            throw "couldn't match to exactly one constructor";
        }
        write-host "    $($constructor.ToString())";
        # note - we don't verify json value types are compatible with the constructor parameter types
        $paramValues = @(
            $constructor.GetParameters() | foreach-object {
                # use "-as" to cast the json value to the correct type for the constructor parameter
                write-host "    $($_.ParameterType.Name) $($_.Name) = '$($property.value.($_.Name))'";
                $property.value.($_.Name) -as $_.ParameterType
            };
        )
        $form.($property.Name) = $constructors[0].Invoke($paramValues);
        continue;
    }

    # don't know what to do with this type
    throw "unhandled property type '$($propertyType.FullName)'";
   
}

由此产生的日志记录输出为:

Size
    constructor
    System.Drawing.Size
    Void .ctor(Int32, Int32)
    Int32 width = '1024'
    Int32 height = '768'
TopMost
    simple type
    System.Boolean
    'True'
Dock
    enum
    System.Windows.Forms.DockStyle
    'Right'

生成的表单如下所示:

$form | fl Size, TopMost, Dock

# Size    : {Width=1024, Height=768}
# TopMost : True
# Dock    : Right

这应该可以处理 * 大多数 * 常见的System.Windows.Forms.Form属性,但是如果您试图将其用作其他类型的通用反序列化器,则可能会很快失败。

lnlaulya

lnlaulya2#

有没有一个单一的、统一的方法,无论是什么类型的方法都能起作用?
可能不会,但您可以改进当前的方法:

  • 使用-as运算符进行类型转换
  • 提前解析类型名称,而不是使用regex来标识它
  • [System.Boolean]添加特殊解析
# resolve the type
$type = $property.type -as [type]

# convert and assign
$form."$($property.Name)" = switch ($type) {
  {$_ -eq [bool]} {
    [bool]::Parse($property.value)
  }
  default {
    $property.value -as $type
  }
}

相关问题