Я изучаю итерацию атрибутов и значений 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
Аналогично stackoverflow.com/questions/18779762/…, но не затрагивает вложенную часть.
Возможно, это то, что вы искали: stackoverflow.com/questions/14970079/…
Я не вижу никаких проблем с обработкой 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
).
PSCustomObject
см.:Использование оператора присваивания увеличения (+=) для построения строки неэффективно, см. этот ответ и Вопросы производительности/сложение строк
Если вы используете PowerShell 6 или новее, нет необходимости использовать PSCustomObject
, вместо этого вы можете использовать параметр -AsHashTable, который возвращает HashTable
s. С HashTable
обращаться немного проще, чем с PSCustomObject
.
Этот последний комментарий на самом деле показывает, что нет большой разницы между PSCustomObject
и HashTable
(или даже любым словарем), если речь идет об объектном графе . Это означает, что при преобразовании графа объекта обратно в строку Json
(используя, например: ConvertTo-Json -Depth 99
), вы получите те же результаты. Надеюсь, я предлагаю: Сделать PSCustomObject более похожим на словарь (#20591)
Кстати, аналогичное поведение существует и для массивов, где ConvertTo-Json
преобразует все, что поддерживает интерфейс IList, в массив Json.
Таким образом, в основном существует только 3 типа узловых структур:
Map
узлыHashTable
, Ordered
и Dictionary[string,string]
) и определенные объекты (например, PSCustomObject
и ComponentModel.Component
).List
узлыIList
.
Обратите внимание, что командлет ConvertFrom-Json
возвращает граф объектов на основе массивов фиксированного размера, что неудобно, если дело доходит до их модификации, однако командлет ConvertTo-Json
просто поддерживает объекты List
вместо array
. Поэтому предлагаю: Добавить переключатель -AsList в ConvertFrom-Json (#24010)Leaf
узлыstring
или примитив), расположенный в конце каждой (встроенной) ветки коллекции.Другими словами: «общий способ анализа вложенных атрибутов», по моему мнению, должен принимать только эти 3 структуры, не будучи строгими к базовому типу .Net.
Одна из моих первых концепций содержится в этом ответе, который вы можете использовать для самостоятельного рекурсивного обхода всего дерева объектного графа, если вы не хотите полагаться на специальный модуль.
$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"
}
}
}
]
}
Чтобы внести ясность: вам нужен инструмент, который дает вам информацию о структуре JSon? Другими словами, автоматизированный способ показать, как перемещаться по различным файлам JSon?