Как преобразовать строковое значение в динамический тип данных в PowerShell? - PullRequest
1 голос
/ 24 апреля 2019

Можно ли присвоить строковое значение переменной другого типа, учитывая, что тип данных заранее неизвестен? Например, в приведенном ниже примере как обновить значения хеша $values без изменения их типов данных:

$values = @{
    "Boolean" = $true
    "Int"     = 5
    "DateTime"= (Get-Date)
    "Array"   = @("A", "B", "C")
}

$stringValues = @{
    "Boolean" = 'false'
    "Int"     = '10'
    "DateTime"= '2019-01-02 14:45:59.146'
    "Array"   = '@("X", "Y", "Z")'
}

"INITIAL VALUES:"
foreach ($key in $values.Keys) {
    ($key + " = " + $values[$key] + " (" + $values[$key].GetType().FullName + ")")
}

"`nUPDATING..."
foreach ($key in $stringValues.Keys) {
    $values[$key] = $stringValues[$key]
}

"`nUPDATED VALUES:"
foreach ($key in $values.Keys) {
    ($key + " = " + $values[$key] + " (" + $values[$key].GetType().FullName + ")")
}

ВЫВОД:

INITIAL VALUES:
DateTime = 04/23/2019 16:54:13 (System.DateTime)
Array = A B C (System.Object[])
Boolean = True (System.Boolean)
Int = 5 (System.Int32)

UPDATING...

UPDATED VALUES:
DateTime = 2019-01-02 14:45:59.146 (System.String)
Array = @("X", "Y", "Z") (System.String)
Boolean = false (System.String)
Int = 10 (System.String)

Мне нужно, чтобы обновленные значения соответствовали исходным типам данных, а не просто конвертировались в System.String.

Я гибок в отношении содержания строк. Например. строка, содержащая логическое значение false, может быть $false / false / [boolean]false / [boolean]$false / и т. д., или строка, содержащая массив, может использовать другое форматирование (в основном, все, что проще для преобразования строки в правильный тип данных).

По сути, я хочу имитировать то, что делает командлет ConvertFrom-Json, когда он устанавливает свойство объекта из строки JSON, только в моем случае у меня нет структуры JSON.

(В случае, если кому-то интересно, что я пытаюсь сделать: я пытаюсь добавить анализатор INI-файлов в мой модуль ConfigFile , и нет, я не могу просто использовать хеш для возврата настроек INI ; Мне нужно загрузить значения в соответствующие PSVariable s, и чтобы это работало, мне нужно преобразовать строки в соответствующие типы данных.)

Ответы [ 4 ]

2 голосов
/ 24 апреля 2019

Вы можете использовать пользовательский класс вместо хеш-таблицы;в отличие от ключей хеш-таблиц, свойства пользовательских классов могут быть специально набраны.

  • С помощью скалярных значений вы можете просто позволить PowerShell выполнять преобразование из строки вместо вас, за исключением того, что для булевых строк необходимоспециальная обработка (см. комментарии в исходном коде ниже).

  • С массивами все сложнее.В приведенном ниже решении используется [System.Management.Automation.PSParser]::Tokenize() для анализа строки, но в настоящее время оно ограничено распознаванием строковых и числовых литералов.

    • Примечание. Заманчиво использовать Invoke-Expression для всего массива, но этобыть угрозой безопасности, потому что это открывает двери для выполнения произвольного кода.В то время как есть законное использование - например, для строки, которая, как известно, представляет число ниже - * обычно следует избегать Invoke-Expression .

(Если выесли вы не хотите определять классы, вы можете получить типы из значений хеш-таблицы $values и использовать [System.Management.Automation.LanguagePrimitives]::ConvertTo() для преобразования строк в эти типы, как показано в ответе LotPings , хотя обратите внимание, что массивы и логические значения по-прежнему нуждаются в специальной обработке, как показано ниже.)

# Define a custom [Values] class
# with specifically typed properties.
class Values {
  [bool]     $Boolean
  [int]      $Int
  [datetime] $DateTime
  [Array]    $Array
}

# Instantiate a [Values] instance
$values = [Values] @{
  Boolean = $true
  Int     = 5
  DateTime= (Get-Date)
  Array   = @("A", "B", "C")
}

$stringValues = @{
  Boolean = 'false'
  Int     = '10'
  DateTime = '2019-01-02 14:45:59.146'
  Array   = '@("X", "Y", "Z")'
}

"INITIAL VALUES:"
foreach ($key in $values.psobject.properties.Name) {
  ($key + " = " + $values.$key + " (" + $values.$key.GetType().FullName + ")")
}

""
"UPDATING..."

foreach ($key in $stringValues.Keys) {
  switch ($key) {
    'Array' {
      # Parse the string representation.
      # Assumptions and limitations:
      #  The array is flat.
      #  It is sufficient to only support string and numeric constants.
      #  No true syntax validation is needed.
      $values.$key = switch ([System.Management.Automation.PSParser]::Tokenize($stringValues[$key], [ref] $null).Where( { $_.Type -in 'String', 'Number' })) {
        { $_.Type -eq 'String' } { $_.Content; continue }
        { $_.Type -eq 'Number' } { Invoke-Expression $_Content; continue }
      }
      continue
    }
    'Boolean' {  # Boolean scalar
      # Boolean strings need special treatment, because PowerShell considers
      # any nonempty string $true
      $values.$key = $stringValues[$key] -notin 'false', '$false'
      continue
    }
    default { # Non-Boolean scalar
      # Let PowerShell perform automatic from-string conversion
      # based on the type of the [Values] class' target property.
      $values.$key = $stringValues[$key]
    }
  }
}

""
"UPDATED VALUES:"
foreach ($key in $values.psobject.properties.Name) {
  ($key + " = " + $values.$key + " (" + $values.$key.GetType().FullName + ")")
}

Это дает:

INITIAL VALUES:
Boolean = True (System.Boolean)
Int = 5 (System.Int32)
DateTime = 04/24/2019 14:45:29 (System.DateTime)
Array = A B C (System.Object[])

UPDATING...

UPDATED VALUES:
Boolean = True (System.Boolean)
Int = 10 (System.Int32)
DateTime = 01/02/2019 14:45:59 (System.DateTime)
Array = X Y Z (System.Object[])
1 голос
/ 24 апреля 2019

Таким образом, вы хотите привести / преобразовать новое значение в тип старого значения.

Идея должна быть приведена из переменной,
здесь есть связанный вопрос powershell-type-cast-using-type-хранимый в переменной

Ответ предполагает:

Вы можете приблизительно эмулировать приведение, используя следующий метод: [System.Management.Automation.LanguagePrimitives] :: ConvertTo ($ Value, $ TargetType)

Следующая измененная процедура показывает: это не так просто, особенно когда новые данные нуждаются в перегрузках / других параметрах в преобразовании.

"UPDATING..."
foreach ($key in $stringValues.Keys) {
    $values[$key] = [System.Management.Automation.LanguagePrimitives]::ConvertTo(
                    $stringValues[$key], $values[$key].gettype())
}

Сообщение об ошибке в моем немецком языке:

Ausnahme beim Aufrufen von "ConvertTo" mit 2 Аргумент (ан): "Der Wert" 2019-01-02 14: 45.59.146 "kann nicht in den Тип" System.DateTime "конвертирует werden. Fehler:" Die Zeichenfolge wurde nicht als gültiges DateTime erkannt. "" В Zeile: 2 Zeichen: 5 + $ values ​​[$ key] = [System.Management.Automation.LanguagePrimitives] ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo: NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId: PSInvalidCastException

И недостаточный результат:

DateTime = 04/24/2019 09:49:19 (System.DateTime)
Array = @("X", "Y", "Z") (System.Object[])
Boolean = True (System.Boolean)
Int = 10 (System.Int32)

Вы можете развить себя в этой идее, обрабатывая старые типы / новые данные более индивидуально.

1 голос
/ 24 апреля 2019

Договорились о вещи Write-Host. На самом деле его следует использовать только для усиления цветопередачи и некоторых конкретных форматов. Вывод на экран по умолчанию, как вы увидите в моем ответе.

Вы могли бы сделать следующее, но эта строка даты немного странная, ну, для меня, ну, я не видел, чтобы кто-нибудь использовал этот формат. Итак, я изменил его для стиля США, но изменил по мере необходимости для вашего языка.

$values = @{
    'Boolean' = $true
    'Int'     = 5
    'DateTime'= (Get-Date)
    'Array'   = @('A', 'B', 'C')
}

$stringValues = @{
    'Boolean' = 'false'
    'Int'     = '10'
    'DateTime'= '2019-01-02 14:45:59'
    'Array'   = "@('X', 'Y', 'Z')"
}

'INITIAL VALUES:'
foreach ($key in $values.Keys) 
{
    "$key = $($values[$key]) $($values[$key].GetType())"
}


"`nUPDATING..."
foreach ($key in $stringValues.Keys) 
{
    switch ($key) 
    { 
        Boolean  {[Boolean]$values[$key] = $stringValues['$'+$key]} 
        Int      {[Int]$values[$key] = $stringValues[$key]} 
        DateTime {[DateTime]$values[$key] = $stringValues[$key]} 
        Array    {[Array]$values[$key] = $stringValues[$key]} 
        default {'The value could not be determined.'}
    }
}


"`nUPDATED VALUES:"
foreach ($key in $values.Keys) 
{
    "$key = $($values[$key]) $($values[$key].GetType())"
}

# Results

INITIAL VALUES:
DateTime = 04/24/2019 01:44:17 datetime
Array = A B C System.Object[]
Boolean = True bool
Int = 5 int

UPDATING...

UPDATED VALUES:
DateTime = 01/02/2019 14:45:59 datetime
Array = @("X", "Y", "Z") System.Object[]
Boolean = False bool
Int = 10 int
0 голосов
/ 25 апреля 2019

Спасибо @LotPings, @ mklement0 и @postanote за то, что дали мне несколько идей, поэтому вот решение, с которым я пойду:

$values = @{
    "Boolean" = $true
    "Int"     = 5
    "DateTime"= (Get-Date)
    "Array"   = @("A", "B", "C")
}

$stringValues = @{
    "Boolean" = 'false'
    "Int"     = '10'
    "DateTime"= '2019-01-31 14:45:59.005'
    "Array"   = 'X,Y,Z'
}

"INITIAL VALUES:"
foreach ($key in $values.Keys) {
    ($key + " = " + $values[$key] + " (" + $values[$key].GetType().FullName + ")")
}

"`nUPDATING..."
foreach ($key in $stringValues.Keys) {
    $value = $stringValues[$key]

    if ($values[$key] -is [Array]) {
        $values[$key] = $value -split ','
    }
    elseif (($values[$key] -is [Boolean]) -or ($values[$key] -is [Switch])) {
        $values[$key] = $value -notin 'false', '$false', '0', ''
    }
    else {
        $values[$key] = [System.Management.Automation.LanguagePrimitives]::ConvertTo($value, $values[$key].GetType())
    }
}

"`nUPDATED VALUES:"
foreach ($key in $values.Keys) {
    ($key + " = " + $values[$key] + " (" + $values[$key].GetType().FullName + ")")
}

ВЫВОД:

INITIAL VALUES:
DateTime = 04/25/2019 09:32:31 (System.DateTime)
Array = A B C (System.Object[])
Boolean = True (System.Boolean)
Int = 5 (System.Int32)

UPDATING...

UPDATED VALUES:
DateTime = 01/31/2019 14:45:59 (System.DateTime)
Array = X Y Z (System.String[])
Boolean = False (System.Boolean)
Int = 10 (System.Int32)

Я изменил формат массива в строковом значении (которое, как я упоминал в вопросе, было опцией). Фактический код, который будет использовать это, будет немного другим, но основная идея здесь. Единственное замечание, которое я заметил, это то, что тип данных массива изменяется с object[] на string[]. В идеале я хотел бы оставить все как есть, но это не изменит функциональность кода, так что все в порядке. Еще раз спасибо всем за идеи и исправления, и, если у вас появятся лучшие альтернативы, не стесняйтесь писать.

...