2016-11-23 1 views
1

У меня есть хеш-perl, полученный из разбора JSON. JSON может быть любым пользовательским API. Цель состоит в том, чтобы получить строку даты/времени и определить, находится ли эта дата/время вне границ в соответствии с заданным пользователем порогом. Единственная проблема, с которой я сталкиваюсь, заключается в том, что perl кажется немного громоздким при работе с хеш-ключом/подразделением. Как я могу просмотреть все ключи и определить, существует ли ключ или подраздел по всему хэшу? Я прочитал много потоков в stackoverflow, но ничего, что точно соответствует моим потребностям. Я только начал перл на прошлой неделе, чтобы у меня что-то не хватало ... Позвольте мне знать, если это так.Анализ любого количества подключений в perl-хеше

Ниже приводится «соответствующий» код/​​субтитры. Для всего кода см: 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 ([email protected]){ 
     print "[ERROR] Response isn't valid JSON. Please verify source data. \[email protected]"; 
     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 ([email protected]){ 
     print "[ERROR] Response isn't valid JSON. Please verify source data. \[email protected]"; 
     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. 
+0

Вы можете показать некоторые из более сложных структур, на которые вы надеетесь иметь дело, и какой ключ (ы) вы ищете в них? – ysth

+0

Я добавил несколько примеров кода. Я мог бы жестко закодировать его, чтобы выглядеть 2 или 3 «слоями» в глубину, но цель состоит в том, чтобы работать над любым выходом, который вы бросаете на него. Таким образом, ему нужно просто перебрать все/все ключи/подразделы и сравнить ключ, который вы определили с ключами/подразделами, и получить данные после согласования. – Jedimaster0

+0

Я в основном работаю с javascript, и этот вид ветерок в JS:/ – Jedimaster0

ответ

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; 
+0

Я рассматривал возможность использования этого метода, но я вообще не согласен с преобразованием объектов в строки. Я предпочитаю использовать объект и, если возможно, получить доступ к их свойствам. Если понадобится, я сделаю именно это. Спасибо за предложение. – Jedimaster0

+0

@ Jedimaster0 Perh hash не имеет свойств, если вы сами их не создаете. Разумеется, вы можете написать свой собственный парсер JSON, если вам нужна скорость или беспокоиться об использовании памяти. Затем вы можете вставить любые свойства, которые вам нравятся, в хеш. Я также заметил, что 'JSON :: XS' и' JSON :: PP' имеют методы фильтрации, которые могут вызывать обратные вызовы во время разбора. Может быть, это то, что можно было использовать? –

+0

@ Jedimaster0 Еще одна альтернатива может заключаться в том, чтобы скопировать исходный код «Hash :: Flatten» и изменить его таким образом, чтобы он не создавал все ненужные ключи и делает только то, что вы хотите. –

2

Вы можете использовать Data::Visitor::Callback для обхода структуры данных. Он позволяет определять обратные вызовы для разных типов данных внутри вашей структуры. Поскольку мы смотрим только на хэш, это относительно просто.

Следующая программа имеет предопределенный список ключей для поиска (они будут введены пользователем в вашем случае). Я преобразовал ваш пример JSON в Perl hashref и включил его в код, потому что преобразование не имеет значения. Программа посещает каждый hashref в этой структуре данных (включая верхний уровень) и выполняет обратный вызов.

Обратные вызовы в Perl - это ссылки на код. Они могут быть созданы двумя способами. Мы делаем анонимную подпрограмму (иногда называемую lambda function на других языках). Обратный вызов получает два аргумента: объект-посетитель и текущая субструктура данных.

Мы будем перебирать все ключи, которые хотим найти, и просто проверить, соответствуют ли они exist в текущей структуре данных. Если мы увидим один, мы считаем его существование в хеше %seen. Использование хэша для хранения вещей, которые мы видели, является распространенной идиомой в Perl.

Мы используем постфикс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 является ответом.

Я сделал только одно ограничение на ключ, но вы можете легко включить несколько проверок в эти коды, или составить список кодов refs и поместить их в массив ref (keys => [ sub(), sub(), sub() ]), а затем повторить его позже.

В обратном вызове посетителя мы также отслеживаем ключи, которые имеют %passed проверку ограничений. Мы вызываем coderef с $coderef->($arg). Если проверка ограничения возвращает 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 
} 

Если вы хотите узнать больше о таблицах отправки, посмотрите на второй главе книги высшего порядка Perl Марком Джейсон Доминус, который законно доступен бесплатно here.

+0

Хороший ответ! «Данные :: Посетитель», кажется, полезный модуль. Но, похоже, используется «Лось», которая может быть слишком тяжелой для этой задачи? –

+0

@ H времена, когда у Лоу был этот ужасный старт-аут, давно прошли. Это не должно быть большой проблемой. – simbabque

 Смежные вопросы

  • Нет связанных вопросов^_^