Итерация атрибутов Json и объектов PSCustom

Я изучаю итерацию атрибутов и значений json из файла json без предварительного знания схемы. У меня есть код, который работает со многими json-файлами или загрузками, которые я тестировал, но у меня возникают пробелы, когда дело доходит до итерации вложенных значений. Я просмотрел различные сообщения, в которых используются Get-Member, .psobject.name и NoteProperty... но, похоже, все они заранее знали о содержимом json. Я проверил это

{
  "FoodChoices": [
    {
      "id": 1,
      "name": "TakeOut",
      "variableGroups": [],
      "variables": {
        "Location": {
          "value": "Fast Food"
        },
        "Beverage": {
          "value": "Soda or Lemondade"
        },
        "Tip": {
          "value": "No Way"
        }
      }
    },
    {
      "id": 2,
      "name": "Gourmet",
      "variableGroups": [],
      "variables": {
        "Location": {
          "value": "Nice Resturant"
        },
        "Beverage": {
          "value": "Dirty Martini"
        },
        "Tip": {
          "value": "Maybe"
        }
      }
    }
  ]
}

но пришлось затруднить итерацию

$jfile = "$PSScriptRoot\FoodChoice.json"

$file = Get-Content $jFile -Raw
$parse = ($file.substring(0,1) -ne "[")
$json = $file | ConvertFrom-Json

$parent = $json | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name
if ($parse -eq $true)
{
   foreach ($p in $parent)
   {
      $d = $json.$p
      if ($d.length -gt 1) { $data = $d }
   }
}
else {$data = $json }
$out = "Key" + "`t" + "Value" + "`n"
$keys = $data | get-member -type properties | Foreach-Object name
foreach ($d in $data)
{
   foreach ($k in $keys)
   {
       $name = $d.$k
       if ($name -match "@")
       {
           $out += "$k`t`n"
           $members = Get-Member -InputObject $name
           foreach ($m in $members)
           {
              if ($m -match "System.Management.Automation.PSCustomObject")
              {
                 $m1 = $m.ToString()
                 $m1 = $m1.Replace("System.Management.Automation.PSCustomObject","") 
                 if ($m1 -match "@")
                 {
                    $m1 = $m1.Replace("@{","")
                    $m1 = $m1.Replace("}","")
                    $m1 = $m1 -split " = "
                    $m2 = $m1[0]
                    $m3 = $m1[2]
                    $out += "$m2`t$m3`n"
                 }
                 else {$out += "$name`t$m1`n"}
              }
           }
       }
       else {$out += "$k`t$name`n"}
   }
}
$out
Exit

Надеемся, что существует универсальный способ анализа вложенных атрибутов типа System.Management.Automation.PSCustomObject.

Скрипт выводит результаты

Key Value
id  1
name    TakeOut
variableGroups  
variables   
 Beverage   Soda or Lemondade
 Location   Fast Food
 Tip    No Way
id  2
name    Gourmet
variableGroups  
variables   
 Beverage   Dirty Martini
 Location   Nice Resturant
 Tip    Maybe

Чтобы внести ясность: вам нужен инструмент, который дает вам информацию о структуре JSon? Другими словами, автоматизированный способ показать, как перемещаться по различным файлам JSon?

Darin 03.07.2024 16:48

Аналогично stackoverflow.com/questions/18779762/…, но не затрагивает вложенную часть.

Twisty 03.07.2024 16:55

Возможно, это то, что вы искали: stackoverflow.com/questions/14970079/…

Twisty 03.07.2024 19:06
Как сделать HTTP-запрос в Javascript?
Как сделать HTTP-запрос в Javascript?
В JavaScript вы можете сделать HTTP-запрос, используя объект XMLHttpRequest или более новый API fetch. Вот пример для обоих методов:
2
3
93
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Я не вижу никаких проблем с обработкой json. Однако я думаю, что основным способом обработки JSON ключа/значения в PowerShell будет использование convertFrom-Json

$jsondata = Get-Content D:\Scripts\jason_sample.txt

$jsondata | ConvertFrom-Json #Should give you all the child underneath and you can expand also. 

($jsondata | ConvertFrom-Json).FoodChoices ## should give you all the id, name , etc 

Если вы все еще боретесь с этим, то сначала вы можете справиться с этим с помощью [System.Text.RegularExpressions.Regex]::Unescape($_)

$jsondata | ConvertTo-Json | % { [System.Text.RegularExpressions.Regex]::Unescape($_) } | Out-File "File.json" -Force

Рассмотрим следующий пример.

function Resolve-Properties($inputObject, $maxLevels=5, $Level=0){

  process {
    if ($Level -eq 0){
        Write-Host "Key`tValue"
    } else {
        for($i = 0; $i -lt $level; $i++){
            Write-Host "`t" -NoNewline
        }
    }
    
    foreach($prop in $InputObject.PSObject.Properties){
      if ($prop.Value.Count -eq 0){
        Write-Host "$($prop.Name)`t[EmptyObj]"
      } elseif ($prop.TypeNameOfValue -match "Object"){
        Write-Host "$($prop.Name)`t[Object]"
        if ($level -lt $maxLevels){
          foreach($s in $prop.Value){
            Resolve-Properties -InputObject $s -Level ($level + 1)
          }
        }
      } else {
        Write-Host "$($prop.Name)`t$($prop.Value)"
      }
    }
  }
}

$file = '{
  "FoodChoices": [
    {
      "id": 1,
      "name": "TakeOut",
      "variableGroups": [],
      "variables": {
        "Location": {
          "value": "Fast Food"
        },
        "Beverage": {
          "value": "Soda or Lemondade"
        },
        "Tip": {
          "value": "No Way"
        }
      }
    },
    {
      "id": 2,
      "name": "Gourmet",
      "variableGroups": [],
      "variables": {
        "Location": {
          "value": "Nice Resturant"
        },
        "Beverage": {
          "value": "Dirty Martini"
        },
        "Tip": {
          "value": "Maybe"
        }
      }
    }
  ]
}'

$parse = ($file.substring(0,1) -ne "[")
$json = $file | ConvertFrom-Json

$parent = $json | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name
if ($parse -eq $true){
   foreach ($p in $parent)   {
      $d = $json.$p
      if ($d.length -gt 1) { $data = $d }
   }
} else {
    $data = $json
}

Clear

$data | ForEach-Object {
    Resolve-Properties $_
}

Это основано на примере, найденном здесь: Как рекурсивно перебирать свойства объекта?

Это выполняет итерацию объектов и рекурсивно отображает их с добавлением дополнительного символа Tab для каждого уровня.

Выход

Key Value
id  1
name    TakeOut
variableGroups  [EmptyObj]
variables   [Object]
    Location    [Object]
        value   Fast Food
Beverage    [Object]
        value   Soda or Lemondade
Tip [Object]
        value   No Way
Key Value
id  2
name    Gourmet
variableGroups  [EmptyObj]
variables   [Object]
    Location    [Object]
        value   Nice Resturant
Beverage    [Object]
        value   Dirty Martini
Tip [Object]
        value   Maybe
Ответ принят как подходящий

Прежде чем ответить на ваш вопрос, я хотел бы начать с нескольких комментариев и замечаний:

  • Именование вашей переменной несколько сбивает с толку: то, что вы называете $json ($file | ConvertFrom-Json), больше не имеет отношения к Json, это объект PowerShell (-Graph), содержащий коллекции в виде массивов (@()) и PSCustomObjects(1) ([PSCustomObject]@{}) или HashTables (@{}), которые содержат другие коллекции или скаляры в виде строк и примитивов. По той же причине переменная $file несколько сбивает с толку, поскольку она больше не имеет никакого отношения к файлу, за исключением содержимого (рассмотрим: $Content или даже явную строку Json (на самом деле $Json).

    1. Подробную информацию о PSCustomObject см.:
  • Использование оператора присваивания увеличения (+=) для построения строки неэффективно, см. этот ответ и Вопросы производительности/сложение строк

  • Если вы используете PowerShell 6 или новее, нет необходимости использовать PSCustomObject, вместо этого вы можете использовать параметр -AsHashTable, который возвращает HashTables. С HashTable обращаться немного проще, чем с PSCustomObject.

Этот последний комментарий на самом деле показывает, что нет большой разницы между PSCustomObject и HashTable (или даже любым словарем), если речь идет об объектном графе . Это означает, что при преобразовании графа объекта обратно в строку Json (используя, например: ConvertTo-Json -Depth 99), вы получите те же результаты. Надеюсь, я предлагаю: Сделать PSCustomObject более похожим на словарь (#20591)

Кстати, аналогичное поведение существует и для массивов, где ConvertTo-Json преобразует все, что поддерживает интерфейс IList, в массив Json.

Таким образом, в основном существует только 3 типа узловых структур:

  • Map узлы
    все, что поддерживает интерфейс IDictionary (например, HashTable, Ordered и Dictionary[string,string]) и определенные объекты (например, PSCustomObject и ComponentModel.Component).
  • List узлы
    Массивы и все, что поддерживает интерфейс IList. Обратите внимание, что командлет ConvertFrom-Json возвращает граф объектов на основе массивов фиксированного размера, что неудобно, если дело доходит до их модификации, однако командлет ConvertTo-Json просто поддерживает объекты List вместо array. Поэтому предлагаю: Добавить переключатель -AsList в ConvertFrom-Json (#24010)
  • Leaf узлы
    Скаляр 🔁 (как string или примитив), расположенный в конце каждой (встроенной) ветки коллекции.

Другими словами: «общий способ анализа вложенных атрибутов», по моему мнению, должен принимать только эти 3 структуры, не будучи строгими к базовому типу .Net.

Одна из моих первых концепций содержится в этом ответе, который вы можете использовать для самостоятельного рекурсивного обхода всего дерева объектного графа, если вы не хотите полагаться на специальный модуль.

В любом случае, набор ObjectGraphTools уже может быть полезен для изучения графа объекта, например. Удобная функция Member-Access Enumeration часто приводит к ловушкам во время рекурсивной итерации. Для этого вы можете использовать командлет Get-ChildNode:

$Object = $Json | ConvertFrom-Json
$Object | Get-ChildNode -Recurse -Leaf

Path                                    Name  Depth             Value
----                                    ----  -----             -----
FoodChoices[0].id                       id        3                 1
FoodChoices[0].name                     name      3           TakeOut
FoodChoices[0].variables.Location.value value     5         Fast Food
FoodChoices[0].variables.Beverage.value value     5 Soda or Lemondade
FoodChoices[0].variables.Tip.value      value     5            No Way
FoodChoices[1].id                       id        3                 2
FoodChoices[1].name                     name      3           Gourmet
FoodChoices[1].variables.Location.value value     5    Nice Resturant
FoodChoices[1].variables.Beverage.value value     5     Dirty Martini
FoodChoices[1].variables.Tip.value      value     5             Maybe

Или создать шаблон выражения PowerShell с помощью командлетов Copy-ObjectGraph и ConvertTo-Expression:

$Object | Copy-ObjectGraph -ListAs Collections.Generic.List[object] -MapAs Ordered |
    ConvertTo-Expression -LanguageMode Full

[ordered]@{
    FoodChoices = [System.Collections.Generic.List[System.Object]]@(
        [ordered]@{
            id = [long]1
            name = [string]'TakeOut'
            variableGroups = [System.Collections.Generic.List[System.Object]]@()
            variables = [ordered]@{
                Location = [ordered]@{ value = [string]'Fast Food' }
                Beverage = [ordered]@{ value = [string]'Soda or Lemondade' }
                Tip = [ordered]@{ value = [string]'No Way' }
            }
        },
        [ordered]@{
            id = [long]2
            name = [string]'Gourmet'
            variableGroups = [System.Collections.Generic.List[System.Object]]@()
            variables = [ordered]@{
                Location = [ordered]@{ value = [string]'Nice Resturant' }
                Beverage = [ordered]@{ value = [string]'Dirty Martini' }
                Tip = [ordered]@{ value = [string]'Maybe' }
            }
        }
    )
}

И если вы хотите использовать этот модуль, вы можете перебрать весь граф объекта несколькими способами:

Заключительная итерация

Создав функцию, которая вызывает сама себя, например:

function Iterate([PSNode]$Node) { # Basic iterator
    if ($Node -is [PSLeafNode]) {
        "{0}{1}: {2}" -f (' ' * $Node.Depth), $Node.Name, $Node.Value 
    }
    else {
        $Node.ChildNodes.foreach{ Iterate $_ }
    }
}

$Object = $Json | ConvertFrom-Json
$PSNode = [PSNode]::ParseInput($Object)
Iterate $PSNode

   id: 1
   name: TakeOut
     value: Fast Food
     value: Soda or Lemondade
     value: No Way
   id: 2
   name: Gourmet
     value: Nice Resturant
     value: Dirty Martini
     value: Maybe

Плоская итерация

Зная, что функции PowerShell довольно дороги (см. этот пост), вы можете вместо этого рассмотреть возможность выполнения «плоской итерации» (которая имеет одинаковый результат):

$LeafNodes = $Object | Get-ChildNode -Recurse -Leaf
foreach ($Node in $LeafNodes) {
    "{0}{1}: {2}" -f (' ' * $Node.Depth), $Node.Name, $Node.Value 
}

Нет итерации

В некоторых случаях вам может вообще не потребоваться итерация, например. где хочу найти подсказку обо всех "Fast Food" локациях. Для этого я разработал Расширенную точечную нотацию (Xdn) , похожую на XPath для объектов XML, которую вы можете использовать вместе с командлетом Get-Node:

$Object | Get-Node ~* = "Fast Food"...Tip.Value

Path                               Name  Depth Value
----                               ----  ----- -----
FoodChoices[0].variables.Tip.value value     5 No Way

Чтобы изменить значение соответствующего узла (в вашем примере), вы можете сделать это:

($Object | Get-Node ~* = "Fast Food"...Tip.Value).Value = 'Never'

Или, если это касается нескольких узлов Tip.Value, это будет:

$Object | Get-Node ~* = "Fast Food"...Tip.Value | ForEach-Object { $_.Value = 'Never' }

Где

$Object | ConvertTo-Json -Depth 9 # Results in:

{
  "FoodChoices": [
{
  "id": 1,
  "name": "TakeOut",
  "variableGroups": [],
  "variables": {
    "Location": {
      "value": "Fast Food"
    },
    "Beverage": {
      "value": "Soda or Lemondade"
    },
    "Tip": {
      "value": "Never"
    }
  }
},
{
  "id": 2,
  "name": "Gourmet",
  "variableGroups": [],
  "variables": {
    "Location": {
      "value": "Nice Resturant"
    },
    "Beverage": {
      "value": "Dirty Martini"
    },
    "Tip": {
      "value": "Maybe"
    }
  }
}
  ]
}

Другие вопросы по теме