在perl哈希

时间:2016-11-23 22:34:56

标签: json perl hash iteration

我有一个从解析JSON获得的perl哈希。 JSON可以是用户定义的API可以生成的任何内容。目标是获取日期/时间字符串,并根据用户定义的阈值确定该日期/时间是否超出范围。我唯一的问题是perl在处理散列键/子键迭代时似乎有点麻烦。如何查看所有键并确定整个哈希中是否存在键或子键?我已经在stackoverflow中读了很多线程,但没有任何东西完全符合我的需求。我上周才开始perl,所以我可能会遗漏一些东西......如果是这样,请告诉我。

以下是“相关”代码/ subs。对于所有代码,请参阅:https://gitlab.com/Jedimaster0/check_http_freshness

use warnings;
use strict;
use LWP::UserAgent;
use Getopt::Std;
use JSON::Parse 'parse_json';
use JSON::Parse 'assert_valid_json';
use DateTime;
use DateTime::Format::Strptime;

# Verify the content-type of the response is JSON
eval {
        assert_valid_json ($response->content);
};
if ( $@ ){
        print "[ERROR] Response isn't valid JSON. Please verify source data. \n$@";
        exit EXIT_UNKNOWN;
} else {
        # Convert the JSON data into a perl hashrefs
        $jsonDecoded = parse_json($response->content);
        if ($verbose){print "[SUCCESS] JSON FOUND -> ", $response->content , "\n";}

        if (defined $jsonDecoded->{$opts{K}}){
                if ($verbose){print "[SUCCESS] JSON KEY FOUND -> ", $opts{K}, ": ", $jsonDecoded->{$opts{K}}, "\n";}
                NAGIOS_STATUS(DATETIME_DIFFERENCE(DATETIME_LOOKUP($opts{F}, $jsonDecoded->{$opts{K}})));
        } else {
                print "[ERROR] Retreived JSON does not contain any data for the specified key: $opts{K}\n";
                exit EXIT_UNKNOWN;
        }
}




sub DATETIME_LOOKUP {
        my $dateFormat = $_[0];
        my $dateFromJSON = $_[1];

        my $strp = DateTime::Format::Strptime->new(
                pattern   => $dateFormat,
                time_zone => $opts{z},
                on_error  => sub { print "[ERROR] INVALID TIME FORMAT: $dateFormat OR TIME ZONE: $opts{z} \n$_[1] \n" ; HELP_MESSAGE(); exit EXIT_UNKNOWN; },
        );

        my $dt = $strp->parse_datetime($dateFromJSON);
        if (defined $dt){
                if ($verbose){print "[SUCCESS] Time formatted using -> $dateFormat\n", "[SUCCESS] JSON date converted -> $dt $opts{z}\n";}
                return $dt;
        } else {
                print "[ERROR] DATE VARIABLE IS NOT DEFINED. Pattern or timezone incorrect."; exit EXIT_UNKNOWN
        }
}




# Subtract JSON date/time from now and return delta
sub DATETIME_DIFFERENCE {
        my $dateInitial = $_[0];
        my $deltaDate;
        # Convert to UTC for standardization of computations and it's just easier to read when everything matches.
        $dateInitial->set_time_zone('UTC');

        $deltaDate = $dateNowUTC->delta_ms($dateInitial);
        if ($verbose){print "[SUCCESS] (NOW) $dateNowUTC UTC - (JSON DATE) $dateInitial ", $dateInitial->time_zone->short_name_for_datetime($dateInitial), " = ", $deltaDate->in_units($opts{u}), " $opts{u} \n";}

        return $deltaDate->in_units($opts{u});
}

样本数据

{
  "localDate":"Wednesday 23rd November 2016 11:03:37 PM",
  "utcDate":"Wednesday 23rd November 2016 11:03:37 PM",
  "format":"l jS F Y h:i:s A",
  "returnType":"json",
  "timestamp":1479942217,
  "timezone":"UTC",
  "daylightSavingTime":false,
  "url":"http:\/\/www.convert-unix-time.com?t=1479942217",
  "subkey":{
    "altTimestamp":1479942217,
    "altSubkey":{
      "thirdTimestamp":1479942217
    }
  }
}

[解决]

我使用了@HåkonHægland提供的答案。以下是代码更改。使用flatten模块,我可以使用任何与JSON键匹配的输入字符串。我还有一些工作要做,但你可以看到问题已经解决了。谢谢@HåkonHægland。

use warnings;
use strict;
use Data::Dumper;
use LWP::UserAgent;
use Getopt::Std;
use JSON::Parse 'parse_json';
use JSON::Parse 'assert_valid_json';
use Hash::Flatten qw(:all);
use DateTime;
use DateTime::Format::Strptime;

# Verify the content-type of the response is JSON
eval {
        assert_valid_json ($response->content);
};
if ( $@ ){
        print "[ERROR] Response isn't valid JSON. Please verify source data. \n$@";
        exit EXIT_UNKNOWN;
} else {
        # Convert the JSON data into a perl hashrefs
        my $jsonDecoded = parse_json($response->content);
        my $flatHash = flatten($jsonDecoded);

        if ($verbose){print "[SUCCESS] JSON FOUND -> ", Dumper($flatHash), "\n";}

        if (defined $flatHash->{$opts{K}}){
                if ($verbose){print "[SUCCESS] JSON KEY FOUND -> ", $opts{K}, ": ", $flatHash>{$opts{K}}, "\n";}
                NAGIOS_STATUS(DATETIME_DIFFERENCE(DATETIME_LOOKUP($opts{F}, $flatHash->{$opts{K}})));
        } else {
                print "[ERROR] Retreived JSON does not contain any data for the specified key: $opts{K}\n";
                exit EXIT_UNKNOWN;
        }
}

示例:

./check_http_freshness.pl -U http://bastion.mimir-tech.org/json.html -K result.creation_date -v
[SUCCESS] JSON FOUND -> $VAR1 = {
          'timestamp' => '20161122T200649',
          'result.data_version' => 'data_20161122T200649_data_news_topics',
          'result.source_version' => 'kg_release_20160509_r33',
          'result.seed_version' => 'seed_20161016',
          'success' => 1,
          'result.creation_date' => '20161122T200649',
          'result.data_id' => 'data_news_topics',
          'result.data_tgz_name' => 'data_news_topics_20161122T200649.tgz',
          'result.source_data_version' => 'seed_vtv: data_20161016T102932_seed_vtv',
          'result.data_digest' => '6b5bf1c2202d6f3983d62c275f689d51'
        };

Odd number of elements in anonymous hash at ./check_http_freshness.pl line 78, <DATA> line 1.
[SUCCESS] JSON KEY FOUND -> result.creation_date:
[SUCCESS] Time formatted using -> %Y%m%dT%H%M%S
[SUCCESS] JSON date converted -> 2016-11-22T20:06:49 UTC
[SUCCESS] (NOW) 2016-11-26T19:02:15 UTC - (JSON DATE) 2016-11-22T20:06:49 UTC = 94 hours
[CRITICAL] Delta hours (94) is >= (24) hours. Data is stale.

2 个答案:

答案 0 :(得分:3)

您可以尝试使用Hash::Flatten。例如:

use Hash::Flatten qw(flatten);

my $json_decoded = parse_json($json_str);
my $flat = flatten( $json_decoded );
say "found" if grep /(?:^|\.)\Q$key\E(?:\.?|$)/, keys %$flat;

答案 1 :(得分:2)

您可以使用Data::Visitor::Callback遍历数据结构。它允许您为结构中的不同类型的数据类型定义回调。由于我们只是查看哈希,因此相对简单。

以下程序有一个预定义的键列表(在您的情况下将是用户输入)。我将您的示例JSON转换为Perl hashref并将其包含在代码中,因为转换不相关。该程序访问此数据结构中的每个hashref(包括顶级)并运行回调。

Perl中的回调是代码引用。这些可以通过两种方式创建。我们正在进行匿名子程序(有时在其他语言中称为lambda function)。回调传递两个参数:访问者对象和当前数据子结构。

我们将迭代我们想要查找的所有密钥,并检查它们是否在当前数据结构中exist。如果我们看到一个,我们会计算它在%seen哈希中的存在。使用哈希来存储我们看到的东西是Perl中常见的习语。

我们在这里使用 postfix if,方便易读。 %seen哈希,因此我们使用$key访问$seen{$key}后面的值,而$data哈希引用,因此我们使用解除引用运算符->$key访问$data->{$key}后面的值。

回调需要我们再次返回$data,以便继续。最后一行就在那里,它并不重要。

我已使用Data::Printer输出%seen哈希值,因为它很方便。如果需要,您还可以使用Data::Dumper。在生产中,你不需要那样。

use strict;
use warnings;
use Data::Printer;
use Data::Visitor::Callback;

my $from_json = {
    "localDate"  => "Wednesday 23rd November 2016 11:03:37 PM",
    "utcDate"    => "Wednesday 23rd November 2016 11:03:37 PM",
    "format"     => "l jS F Y h:i:s A",
    "returnType" => "json",
    "timestamp"  => 1479942217,
    "timezone"   => "UTC",
    "daylightSavingTime" =>
        0,    # this was false, I used 0 because that's a non-true value
    "url"    => "http:\/\/www.convert-unix-time.com?t=1479942217",
    "subkey" => {
        "altTimestamp" => 1479942217,
        "altSubkey"    => {
            "thirdTimestamp" => 1479942217
        }
    }
};

my @keys_to_find = qw(timestamp altTimestamp thirdTimestamp missingTimestamp);

my %seen;
my $visitor = Data::Visitor::Callback->new(
    hash => sub {
        my ( $visitor, $data ) = @_;

        foreach my $key (@keys_to_find) {
            $seen{$key}++ if exists $data->{$key};
        }

        return $data;
    },
);
$visitor->visit($from_json);

p %seen;

该程序输出以下内容。请注意,这是 Perl数据结构。 Data :: Printer不是一个序列化程序,它是一种使数据易于读取的工具。

{
    altTimestamp     1,
    thirdTimestamp   1,
    timestamp        1
}

既然你也想限制输入,这里有一个例子。以下程序是对上述程序的修改。它允许为每个必需的密钥提供一组不同的约束。

我已经使用a dispatch table完成了这项工作。从本质上讲,这是一个包含代码引用的哈希。有点像我们为访客使用的回调。

我所包含的限制是用日期做一些事情。在Perl中使用日期的简单方法是核心模块Time::Piece。这里有很多关于各种日期事物的问题,其中Time :: Piece就是答案。

我每个键只做了一个约束,但你可以轻松地在这些代码引用中包含几个检查,或者创建一个代码引用列表并将它们放在数组引用(keys => [ sub(), sub(), sub() ])中然后稍后重复。

在访问者回调中,我们现在还跟踪%passed约束检查的密钥。我们使用$coderef->($arg)调用coderef。如果约束检查返回a true value,则会在哈希中记录它。

use strict;
use warnings;
use Data::Printer;
use Data::Visitor::Callback;
use Time::Piece;
use Time::Seconds; # for ONE_DAY

my $from_json = { ... }; # same as above

# prepare one of the constraints
# where I'm from, Christmas eve is considered Christmas
my $christmas = Time::Piece->strptime('24 Dec 2016', '%d %b %Y');

# set up the constraints per required key
my %constraints = (
    timestamp        => sub {
        my ($epoch) = @_;
        # not older than one day

        return $epoch < time && $epoch > time - ONE_DAY;
    },
    altTimestamp     => sub {
        my ($epoch) = @_;
        # epoch value should be an even number

        return ! $epoch % 2;
    },
    thirdTimestamp   => sub {
        my ($epoch) = @_;
        # before Christmas 2016

        return $epoch < $christmas;
    },
);

my %seen;
my %passed;
my $visitor = Data::Visitor::Callback->new(
    hash => sub {
        my ( $visitor, $data ) = @_;

        foreach my $key (%constraints) {
            if ( exists $data->{$key} ) {
                $seen{$key}++;
                $passed{$key}++ if $constraints{$key}->( $data->{$key} );
            }
        }

        return $data;
    },
);
$visitor->visit($from_json);

p %passed;

这次的输出是:

{
    thirdTimestamp   1,
    timestamp        1
}

如果您想了解有关调度表的更多信息,请查看Mark Jason Dominus撰写的 Higher Order Perl 一书的第二章,该书合法免费提供here。< / p>