【问题标题】:Convert JS format csv to a delimited CSV using powershell使用 powershell 将 JS 格式的 csv 转换为带分隔符的 CSV
【发布时间】:2020-06-22 22:32:12
【问题描述】:

我有一个示例 csv 文件并尝试使用 powershell 转换为分隔格式的 csv。时间戳部分,默认存储为秒,想知道能不能转换成"hh:mm"

不太清楚我应该从哪里开始。

感谢您的帮助!

样本.csv

{
   "Body" : {
      "inverter/1" : {
         "Data" : {
            "Current_DC_String_1" : {
               "Unit" : "A",
               "Values" : {
                  "0" : 0,
                  "300" : 0,
                  "600" : 0,
                  "900" : 0
               },
               "_comment" : "channelId=66050"
            },
            "Current_DC_String_2" : {
               "Unit" : "A",
               "Values" : {
                  "0" : 0,
                  "300" : 0,
                  "600" : 0,
                  "900" : 0
               },
               "_comment" : "channelId=131586"
            },
            "EnergyReal_WAC_Sum_Produced" : {
               "Unit" : "Wh",
               "Values" : {
                  "0" : 0,
                  "300" : 0,
                  "600" : 0,
                  "900" : 0
               },
               "_comment" : "channelId=67830024"
            },
            "Voltage_DC_String_1" : {
               "Unit" : "V",
               "Values" : {
                  "0" : 7.3000000000000007,
                  "300" : 7.3000000000000007,
                  "600" : 7.9000000000000004,
                  "900" : 7.7000000000000002
               },
               "_comment" : "channelId=66049"
            },
            "Voltage_DC_String_2" : {
               "Unit" : "V",
               "Values" : {
                  "0" : 4.2000000000000002,
                  "300" : 4.2000000000000002,
                  "600" : 4.5,
                  "900" : 4.4000000000000004
               },
               "_comment" : "channelId=131585"
            }
         },
         "DeviceType" : 233,
         "End" : "2020-03-11T23:59:59+11:00",
         "NodeType" : 97,
         "Start" : "2020-03-11T00:00:00+11:00"
      },
      "inverter/2" : {
         "Data" : {
            "Current_DC_String_1" : {
               "Unit" : "A",
               "Values" : {
                  "0" : 0,
                  "300" : 0,
                  "600" : 0,
                  "900" : 0
               },
               "_comment" : "channelId=66050"
            },
            "Current_DC_String_2" : {
               "Unit" : "A",
               "Values" : {
                  "0" : 0,
                  "300" : 0,
                  "600" : 0,
                  "900" : 0
               },
               "_comment" : "channelId=131586"
            },
            "EnergyReal_WAC_Sum_Produced" : {
               "Unit" : "Wh",
               "Values" : {
                  "0" : 0,
                  "300" : 0,
                  "600" : 0,
                  "900" : 0
               },
               "_comment" : "channelId=67830024"
            },
            "Voltage_DC_String_1" : {
               "Unit" : "V",
               "Values" : {
                  "0" : 6.7000000000000002,
                  "300" : 7,
                  "600" : 6.8000000000000007,
                  "900" : 7.2000000000000002
               },
               "_comment" : "channelId=66049"
            },
            "Voltage_DC_String_2" : {
               "Unit" : "V",
               "Values" : {
                  "0" : 2.2000000000000002,
                  "300" : 2.3000000000000003,
                  "600" : 2.2000000000000002,
                  "900" : 2.2000000000000002
               },
               "_comment" : "channelId=131585"
            }
         },
         "DeviceType" : 233,
         "End" : "2020-03-11T23:59:59+11:00",
         "NodeType" : 98,
         "Start" : "2020-03-11T00:00:00+11:00"
      }
   },
   "Head" : {
      "RequestArguments" : {
         "Query" : "Inverter+SensorCard+Meter",
         "Scope" : "System"
      },
      "Status" : {
         "Code" : 0,
         "Reason" : "",
         "UserMessage" : ""
      },
      "Timestamp" : "2020-03-11T01:00:03+11:00"
   }
}

使用默认时间戳转换的预期结果

或者如果可能的话,可以在转换时间前面添加从“开始”解析的日期“2020-03-11”:“2020-03-11T00:00:00+11:00”,为每一行生成 DateTimestamp。

【问题讨论】:

    标签: json powershell csv


    【解决方案1】:

    假设您的数据实际上是 JSON 文件,而不是 CSV 文件,您可以尝试以下方法。如果使用 -AsHashTable 开关将 JSON 文件转换为带有 ConvertFrom-Json 的 System.Collections.Hashtable 对象。您可以从Looping through a hash, or using an array in PowerShell 阅读如何遍历哈希表属性。

    然后,您可以获取 System.Management.Automation.PSCustomObject 行并通过管道传送到 Export-Csv,这将创建 CSV 文件。此外,您可以使用[System.Timespan]::FromSeconds() 获取时间跨度,它将总秒数转换为System.Timespan 类型的对象,并使用System.TimeSpan.ToString() 格式化hh:mm。有关时间跨度格式的更多信息,您可以查看Convert seconds to hh:mm:ss,fff format in PowerShell。

    作为额外的清理步骤,我还删除了 " 和 Set-Content 的引号。如果您希望 " 保留在您的文件中,则不需要这样做。

    $json = Get-Content -Path .\sample.json | ConvertFrom-Json -AsHashtable
    
    $json.Body.GetEnumerator() | ForEach-Object {
        $inverter = $_.Key
    
        $_.Value.Data.GetEnumerator() | ForEach-Object {
            $value = $_.Key
    
            $_.Value.Values.GetEnumerator() | ForEach-Object {
                [PSCustomObject]@{
                    Inverter = "$inverter $value"
                    Second = [timespan]::FromSeconds($_.Key).ToString("hh\:mm")
                    Value = $_.Value
                }
            }
        }
    } | Export-Csv -Path .\sample.csv
    # Use NoTypeINformation to remove #TYPE from headers in < Powershell 6
    
    Set-Content -Path .\sample.csv -Value ((Get-Content -Path .\sample.csv) -replace '"')
    

    sample.csv

    Inverter,Second,Value
    inverter/1 Current_DC_String_2,00:05,0
    inverter/1 Current_DC_String_2,00:10,0
    inverter/1 Current_DC_String_2,00:15,0
    inverter/1 Current_DC_String_2,00:00,0
    inverter/1 Current_DC_String_1,00:05,0
    inverter/1 Current_DC_String_1,00:10,0
    ...
    

    性能改进

    正如mklement0 所解释的,当使用.NET 类型或[PSCustomObject] 时,成员枚举比使用管道快得多。您可以从这个有用的answer 中了解更多信息。

    下面是使用foreach枚举而不是Foreach-Object进行改进的简单用法。

    $json = Get-Content -Path .\sample.json | ConvertFrom-Json -AsHashtable
    
    $csvRows = @()
    
    foreach ($inverter in $json.Body.GetEnumerator()) {
    
        foreach ($outerValue in $inverter.Value.Data.GetEnumerator()) {
    
            foreach ($innerValue in $outerValue.Value.Values.GetEnumerator()){
                $csvRowData = [PSCustomObject]@{
                    Inverter = "$($inverter.Key) $($outerValue.Key)"
                    Second = [timespan]::FromSeconds($innerValue.Key).ToString("hh\:mm")
                    Value = $innerValue.Value
                }
    
                $csvRows += $csvRowData;
            }
        }
    }
    
    $csvRows | Export-Csv -Path .\sample.csv
    
    Set-Content -Path .\sample.csv -Value ((Get-Content -Path .\sample.csv) -replace '"')
    

    【讨论】:

      【解决方案2】:

      您的输入文件是 JSON 文件,而不是 CSV 文件。

      为了将其对象图展平为您需要的 CSV 行列结构,需要嵌套循环:

      # Parse the JSON file into custom objects.
      $fromJson = Get-Content -Raw file.json | ConvertFrom-Json
      
      & {
        foreach ($inverter in $fromJson.Body.psobject.Properties.Name) {
          $date =  $fromJson.Body.$inverter.Start
          if ($date -is [datetime]) { $date = $date.ToString('yyyy-MM-dd') }
          else                      { $date = ($date -csplit 'T')[0] }
          foreach ($measurement in $fromJson.Body.$inverter.Data.psobject.Properties.Name) {
            foreach ($valueProp in $fromJson.Body.$inverter.Data.$measurement.Values.psobject.Properties) {
              [pscustomobject] @{
                Inverter  = "$inverter $measurement"
                TimeStamp = $date + ' ' + 
                            [timespan]::FromSeconds([int] $valueProp.Name).ToString('hh\:mm')
                Value     = $valueProp.Value
              }
            }
          }
        } 
      } | ConvertTo-Csv  # output CSV data as an array of *strings*; 
                         # to save to a *file*, use something like:
                         # Export-Csv -NoTypeInformation out.csv
      

      注意.psobject.Properties 是如何用于反映给定对象的属性的; .psobject 是一个通常隐藏在任何对象上的属性,它提供反射信息比Get-Member cmdlet 更方便快捷。

      另请注意,ConvertFrom-Json 如何解析 JSON 中的时间戳取决于 PowerShell 版本(版本):

      • Windows PowerShell 将它们解析为 字符串,因此将字符串按T 分割并取其之前的内容就足够了。

      • PowerShell [Core] 将它们解析为 [datetime] 实例,以 本地 时间表示,因此它们只保证在同一日历日产生如果本地时区与 JSON 值 (+11:00) 中的 UTC 偏移所暗示的时区相同。


      选读:性能考虑:

      注意在 管道 中使用嵌套的foreach 循环 而不是使用ForEach-Object cmdlet 以获得更好的性能。 有关背景信息,请参阅this answer。

      但是,对于可能无关紧要的小输入文件,以及使用嵌套管道的 RoadRunner's helpful hashtable-based alternative 在实践中可能足够快 - 而且也可以使用 foreach 循环 代替(更新:它现在在第二个命令中)。

      通过
      -AsHashtable:将 JSON 解析为哈希表([hashtable],又名 System.Collections.Hashtable):

      • 具有需要更少内存的优势(ConvertFrom-Json 默认输出的[pscustomobject] 实例的内部存储效率有些低)。

      • 具有不保留属性输入顺序的潜在缺点,因为[hashtable] 条目本质上是无序的;但是,在目前的情况下,这不是问题,因为创建了具有固定属性顺序的不同输出对象。

      【讨论】:

        猜你喜欢
        • 2020-11-24
        • 1970-01-01
        • 1970-01-01
        • 2023-03-14
        • 1970-01-01
        • 1970-01-01
        • 2013-06-11
        • 2015-11-30
        • 1970-01-01
        相关资源
        最近更新 更多