Parse JSON-like markup in perl - json

I am not sure what markup this is to be honest, I though at first JSON but using parse_json from JSON::Parse fails with: JSON error at line 2, byte 15/1380740: Unexpected character '{' parsing initial state: expecting whitespace: '\n', '\r', '\t', ' ' at ....
This is what I parse into an hash: https://steamcdn-a.akamaihd.net/apps/730/scripts/items/items_game.d8a302f03758b99ab65b60b3a4a11d73ca4738bd.txt.
What I tried:
use strict;
use warnings;
use LWP::UserAgent;
use JSON::Parse 'parse_json';
my $ua = LWP::UserAgent->new;
my $response = $ua->get( "https://steamcdn-a.akamaihd.net/apps/730/scripts/items/items_game.d8a302f03758b99ab65b60b3a4a11d73ca4738bd.txt" );
if ( $response->is_success ) {
my $game_items = parse_json( $response->content );
# ... do stuff
}
Am I doing something wrong? Is this JSON or must I create some hack-ish solution to parse this?
I could not find any suggestions through the "Questions that may already have your answer" section though I think it would be easier if I knew the name of this markup.

This will process your data, It's a bit hacky but it does the job!
use strict;
use warnings;
use autodie;
use Data::Dump;
use LWP::Simple qw/ mirror /;
use constant URL => 'https://steamcdn-a.akamaihd.net/apps/730/scripts/items/items_game.d8a302f03758b99ab65b60b3a4a11d73ca4738bd.txt';
use constant MIRROR => 'steamcdn.txt';
my $data = do {
mirror URL, MIRROR;
open my $fh, '<', MIRROR;
local $/;
<$fh>;
};
my ($hash, $key);
my #stack;
while ( ) {
if ( $data =~ / \G \s* " ([^"]*) " /gcx ) {
if ( defined $key ) {
$hash->{$key} = $1;
$key = undef;
}
else {
$key = $1;
}
}
elsif ( $data =~ / \G \s* \{ /gcx ) {
push #stack, [ $hash, $key ];
$key = $hash = undef;
}
elsif ( $data =~ / \G \s* \} /gcx ) {
die "Structure unbalanced" if defined $key or #stack == 0;
my ($parent, $key) = #{ pop #stack };
$parent->{$key} = $hash;
$hash = $parent;
}
else {
last;
}
}
die "Structure unbalanced" if #stack;
dd $hash;
output
{
items_game => {
alternate_icons2 => {
weapon_icons => {
65604 => {
icon_path => "econ/default_generated/weapon_deagle_hy_ddpat_urb_light",
},
65605 => {
icon_path => "econ/default_generated/weapon_deagle_hy_ddpat_urb_medium",
},
65606 => {
icon_path => "econ/default_generated/weapon_deagle_hy_ddpat_urb_heavy",
},
65684 => {
icon_path => "econ/default_generated/weapon_deagle_aa_flames_light",
},
65685 => {
icon_path => "econ/default_generated/weapon_deagle_aa_flames_medium",
},
65686 => {
icon_path => "econ/default_generated/weapon_deagle_aa_flames_heavy",
},
65696 => {
icon_path => "econ/default_generated/weapon_deagle_so_night_light",
},
65697 => {
icon_path => "econ/default_generated/weapon_deagle_so_night_medium",
},
65698 => {
icon_path => "econ/default_generated/weapon_deagle_so_night_heavy",
},
65780 => {
icon_path => "econ/default_generated/weapon_deagle_aa_vertigo_light",
},
65781 => {
icon_path => "econ/default_generated/weapon_deagle_aa_vertigo_medium",
},
65782 => {
icon_path => "econ/default_generated/weapon_deagle_aa_vertigo_heavy",
},
65896 => {
icon_path => "econ/default_generated/weapon_deagle_hy_mottled_sand_light",
},
65897 => {
icon_path => "econ/default_generated/weapon_deagle_hy_mottled_sand_medium",
},
65898 => {
icon_path => "econ/default_generated/weapon_deagle_hy_mottled_sand_heavy",
},
66276 => {
icon_path => "econ/default_generated/weapon_deagle_am_scales_bravo_light",
},
66277 => {
icon_path => "econ/default_generated/weapon_deagle_am_scales_bravo_medium",
},
66278 => {

my $file = do { local $/; <> };
my #stack = [];
my %handlers = (
'"' => sub {
/\G ([^"]*) " /xgc
or die("Unterminated \"\n");
push(#{ $stack[-1] }, $1);
},
'{' => sub {
die("Expected string\n") if #{ $stack[-1] } % 2 == 0;
push(#stack, []);
},
'}' => sub {
die("Unmatched \"}\"\n") if #stack == 1;
my $hash = pop(#stack);
die("Missing value\n") if #$hash % 2 == 1;
push(#{ $stack[-1] }, { #$hash });
},
);
my $data;
for ($file) {
while (1) {
my $next_char = /\G \s* (\S) /gcx ? $1 : last;
my $handler = $handlers{$next_char}
or die("Unrecognized character \"$next_char\"\n");
$handler->();
}
die("Unmatched \"{\"\n") if #stack > 1;
my $hash = pop(#stack);
die("Missing value\n") if #$hash % 2 == 1;
$data = { #$hash };
}
Aside from a simpler stack than Borodin's and the use of a dispatch table instead of a long sequence of "if", this version provides proper error detection. This will detect truncated documents as well as unsupported features.

Related

How to apply pagination for custom endpoint request in wordpress?

I have created a custom restful API endpoint in WordPress which returns the JSON with the only required fields.
So with this one when I go to the example.com/wp-json/wl/posts, it returns 5 posts as I have limited the number of the posts.
function wl_posts() {
$args = [
'numberposts' => 99999,
'post_type' => 'post'
];
$posts = get_posts($args);
$data = [];
$i = 0;
foreach($posts as $post) {
$data[$i]['id'] = $post->ID;
$data[$i]['title'] = $post->post_title;
$data[$i]['content'] = $post->post_content;
$data[$i]['slug'] = $post->post_name;
$data[$i]['featured_image']['thumbnail'] = get_the_post_thumbnail_url($post->ID, 'thumbnail');
$i++;
}
return $data;
}
add_action('rest_api_init', function() {
register_rest_route('wl/v1', 'posts', [
'methods' => 'GET',
'callback' => 'wl_posts',
]);
});
But I also want to add the pagination, so if I add ?page=2 , it should return another 5 posts.
How can that be archived?
When visiting /?rest_route=/ or /wp-json/wp/v2/pages you can drill down into ie. wp/v2/pages/endpoints/0/args then check with page and per_page
curl http://YOUR-SITE/wp-json/wl/v1/posts/?per_page=1&page=2
Publish arguments for reference
We can define and publish these as arguments. This is not required but they are now in line with posts and pages
add_action('rest_api_init', function() {
register_rest_route('wl/v1', 'posts', [
'methods' => 'GET',
'callback' => 'wl_posts',
'args' => [
'page' => [
'description' => 'Current page',
'type' => "integer",
],
'per_page' => [
'description' => 'Items per page',
'type' => "integer",
]
],
]);
});
Fetch arguments
As get_posts has its own logic and it uses WP_Query in the end let's use WP_Query for the better.
function wl_posts() {
$args = [];
if (isset($_REQUEST['per_page'])) {
$args['posts_per_page'] = (int) $_REQUEST['per_page'];
}
if (isset($_REQUEST['page'])) {
$args['page'] = (int) $_REQUEST['page'];
}
$args['post_type'] = 'post';
$get_posts = new WP_Query;
$posts= $get_posts->query( $args );
$data = [];
$i = 0;
foreach($posts as $post) {
$data[$i]['id'] = $post->ID;
$data[$i]['title'] = $post->post_title;
$data[$i]['content'] = $post->post_content;
$data[$i]['slug'] = $post->post_name;
$data[$i]['featured_image']['thumbnail'] =
get_the_post_thumbnail_url($post->ID, 'thumbnail');
$i++;
}
return $data;
}

How to use ACF into JSON via ajax in Wordpress

I'm trying to use the Advanced Custom Field (ACF) plugin to return JSON via ajax in WordPress.
The below code correctly returns the JSON data of the posts but is not giving me the ACF data inside each post.
How can I get that ACF data into JSON?
PHP File
add_shortcode("owt-ajax-shortcode", "owt_wpl_run_shortcode");
function owt_wpl_run_shortcode() {
ob_start();
include_once plugin_dir_path(__FILE__) . 'views/owt-ajax-page-view.php';
$template = ob_get_contents();
ob_end_clean();
echo $template;
}
add_action("wp_enqueue_scripts", "owt_include_scripts");
function owt_include_scripts() {
wp_enqueue_script("jquery");
}
add_action("wp_ajax_owt_ajax_lib", "owt_lib_ajax_handler_fn");
add_action("wp_ajax_nopriv_owt_ajax_lib", "owt_lib_ajax_handler_fn");
function owt_lib_ajax_handler_fn() {
$param = isset($_REQUEST['param']) ? trim($_REQUEST['param']) : "";
global $wpdb;
if (!empty($param)) {
if ($param == "get_all_posts") {
$all_posts = get_posts(array(
"post_type" => "post",
"post_status" => "publish",
'posts_per_page' => 20,
'order' => 'DESC',
'orderby' => 'date',
'cat' => 66
));
header( 'Content-Type: application/json; charset=' . get_option( 'blog_charset' ) );
echo json_encode( $all_posts );
die;
}
}
wp_die();
}
JavaScript
<button id="btn-get-all-posts">Click to get All Posts</button>
<script>
jQuery(function () {
var ajaxurl = "<?php echo admin_url('admin-ajax.php'); ?>";
jQuery("#btn-get-all-posts").on("click", function () {
var postdata = "action=owt_ajax_lib&param=get_all_posts";
jQuery.post(ajaxurl, postdata, function (response) {
console.log(response);
});
});
});
</script>
instead of use get_posts try your own query with WP_Query
here is an example code, please let me know if it works correctly(I didn't test it yet)
function owt_lib_ajax_handler_fn()
{
$param = isset($_REQUEST[ 'param' ]) ? trim($_REQUEST[ 'param' ]) : "";
if ( ! empty($param) ){
if ( $param == "get_all_posts" ){
$args = [
"post_type" => "post",
"post_status" => "publish",
"posts_per_page" => 20,
"order" => 'DESC',
"orderby" => 'date',
"category__in" => [ 66 ],
];
$all_posts = new WP_Query($args);
$json_response = [];
if ( $all_posts->have_posts() ){
while ( $all_posts->have_posts() ) {
$json_response[] = [
'title' => get_the_title(),
'content' => get_the_content(),
'acf_meta' => [
get_fields(get_the_ID()),
],
];
}
wp_send_json($json_response, 200);
wp_die();
}
else {
wp_send_json($json_response, 404);
wp_die();
}
}
}
wp_die();
}
please have a look as below:
$posts = get_posts(array(
'numberposts' => -1,
'post_type' => 'post',
'meta_query' => array(
'relation' => 'AND',
array(
'key' => 'color',
'value' => array('red', 'orange'),
'compare' => 'IN',
),
array(
'key' => 'featured',
'value' => '1',
'compare' => '=',
),
),
));
Please use below function:
add_action("wp_ajax_owt_ajax_lib", "owt_ajax_lib");
add_action("wp_ajax_nopriv_owt_ajax_lib", "owt_ajax_lib");
function owt_ajax_lib() {
$param = isset($_REQUEST['param']) ? trim($_REQUEST['param']) : "";
global $wpdb;
if (!empty($param)) {
if ($param == "get_all_posts") {
$args = [
"post_type" => "post",
"post_status" => "publish",
"posts_per_page" => 20,
"order" => 'DESC',
"orderby" => 'date',
"category__in" => [ 33 ],
];
$all_posts = new WP_Query($args);
$json_response = array();
if ( $all_posts->have_posts() ):
while ( $all_posts->have_posts() ) : $all_posts->the_post();
$json_response[] = [
'title' => get_the_title(),
'content' => get_the_content(),
'acf_meta' => [
get_fields( get_the_ID() ),
],
];
endwhile;
endif;
echo json_encode($json_response);
die;
}
}
die;
}

JSON sort by specific value - PERL

i have this example code
#!/usr/bin/perl
use strict;
use warnings;
use JSON::PP qw( );
use Data::Dumper qw (Dumper);
my $json = JSON::PP->new()->pretty->utf8; # lesbares JSON | Sort numerically
my %ORDER = (id => 1, name => 2);
$json->sort_by(sub {
($ORDER{$JSON::PP::a} // 999) <=> ($ORDER{$JSON::PP::b} // 999)
or $JSON::PP::a cmp $JSON::PP::b
});
print $json->encode(
[
{name => 'ABS700', id => 0, data => [
{
dmsg => 's4F038300', state => 'T: 3.3', user => 'SD_Protocol'
}
]
},
{name => 'GT-WT-02', id => 0, data => [
{
dmsg => 's5410AC5F9800', state => 'T: 17.2 H: 47', user => 'Ralf9'
}
]
},
{name => 'NEU', id => 99, data => [
{
dmsg => 's5410AC5F9800', state => 'T: 17.2 H: 47', user => 'NEUER'
}
]
},
{name => 'Ventus W132', id => 4, data => [
{
dmsg => 'sD66EE1603000', user1 => 'dirigent', comment => 'wind',
readings => [{ state => 'windGuest: 1.2 winddir:0' }]
}
]
},
],
);
I would like to sort this, that the value with the "id => 99" appears at the end.
I could sort all internal values ​​arbitrarily but I need the new outer sorting.
How do I solve this problem?
->sort_by is used to control the order of the elements of hashes.
You want to control the order of the elements of an array.
There's no equivalent mechanism to ->sort_by for arrays because there's no need for one. While you can't naturally control the order in which a hash returns its elements, you can naturally control the order in which an array returns its elements.
my $data = [ ... ];
#$data = sort { $a->{id} <=> $b->{id} } #$data;
print $json->encode($data);

Perl dereferencing individual elements in array of hash of hash

I have a structure as below which contains array of hash of hash of hash. I am getting error while dereferencing the values from the hash.
$VAR1 = \{
'2001' => {
'Arunachal Pradesh' => {
'CHANGLANG' => [
{
'wheat' => '2',
'cotton' => '',
'rice' => '1'
}
],
'SUBANSIRI UPPER' => [
{
'wheat' => '',
'cotton' => '1',
'rice' => '2'
}
],
},
'Andhra Pradesh' => {
'CHITTOOR' => [
{
'wheat' => '34',
'cotton' => '14',
'rice' => '27'
}
],
'VIZIANAGARAM' => [
{
'wheat' => '2',
'cotton' => '',
'rice' => '8'
}
],
}
}
};
I am trying to dereferencing individual values such that I can fill these values to a mysql database. But I getting error "Use of uninitialized value $state in concatenation (.) or string" while derefrencing individual value itself. The code is as follows:
while (my ($key, $href) = each(%$stat) ) {
my $state = $stat->{$state_name}; #where the first value is the state name & the second value is the district
print "$state\n";
}
The state name code is as follows:
if ($line =~ m/^State:,(\w+\s\w+),/){
$state_name = $1;
$stat->{$year}->{$state_name} = {};
}
Any other way via which I can get individual values or I need to assign it to another hash and so forth. Thank you.
To step through your structure properly, you need a loop more like this:
while (my ($year, $year_ref) = each(%$stat) )
{
while (my ($state, $state_ref) = each(%$year_ref) )
{
print "year = $year, state = $state\n";
}
}
You can add additional levels of loop below that if you want to iterate through the entire structure to flatten it.
For example, since you have five levels in your structure, and the level just below the last is an array reference:
while (my ($year, $year_ref) = each(%$stat) )
{
while (my ($state, $state_ref) = each(%$year_ref) )
{
while (my ($city, $city_ref) = each(%$state_ref) )
{
foreach my $prod_rec ( #$city_ref )
{
while (my ($prod, $qty) = each(%$prod_rec) )
{
print "year = $year, state = $state, city = $city, prod = $prod, qty = $qty\n";
}
}
}
}
}
(Please forgive me if I guessed wrong naming the level under $state as $city. It is just a guess.)
Learn how to "walk" through the nested hashes/array. Check below code.
#!/usr/bin/perl
use warnings;
use strict;
my $VAR1 = {
'2001' => {
'Arunachal Pradesh' => {
'CHANGLANG' => [
{
'wheat' => '2',
'cotton' => '',
'rice' => '1'
}
],
'SUBANSIRI UPPER' => [
{
'wheat' => '',
'cotton' => '1',
'rice' => '2'
}
],
},
'Andhra Pradesh' => {
'CHITTOOR' => [
{
'wheat' => '34',
'cotton' => '14',
'rice' => '27'
}
],
'VIZIANAGARAM' => [
{
'wheat' => '2',
'cotton' => '',
'rice' => '8'
}
],
}
}
};
foreach my $a (keys %{ $VAR1->{2001} })
{
print $a."\n";
foreach my $b (keys %{ $VAR1->{2001}->{$a} })
{
print "\t".$b."\n";
foreach my $c ( #{ $VAR1->{2001}->{$a}->{$b} })
{
#print $c."\n";
foreach my $d ( keys %{ $c })
{
print "\t\t $d ===> $c->{$d} \n";
}
}
}
}
OutPut:
Arunachal Pradesh
CHANGLANG
rice ===> 1
wheat ===> 2
cotton ===>
SUBANSIRI UPPER
rice ===> 2
wheat ===>
cotton ===> 1
Andhra Pradesh
CHITTOOR
rice ===> 27
wheat ===> 34
cotton ===> 14
VIZIANAGARAM
rice ===> 8
wheat ===> 2
cotton ===>
In above code, I am hitting each and every element of the hash and printing it manually. This way you can capture any element in the hash and then use it later.

get all nodes from multi level hash in perl

I want all nodes of each key sorted by key in hash ref or array or something like so that I can iterate that according to my need since I have to display each key with its all children.
following is my data structure:
$hash1 = {
'3' => {
'title' => 'Parent-3',
'parentid' => '-1'
},
'1' => {
'children' => {
'11' => {
'title' => 'child-1',
},
'5' => {
'children' => {
'8' => {
'title' => 'first child of child-2',
},
'13' => {
'title' => 'second child of child-2',
}
},
'title' => 'child-2',
}
},
'title' => 'Parent-1',
'parentid' => '-1'
},
'2' => {
'title' => 'Parent-2',
'parentid' => '-1'
},
'4' => {
'title' => 'Parent-4',
'parentid' => '-1'
},
};
I have used following functions :
sub Options {
my $hash = shift;
my $options = '';
my $iter; $iter = sub {
my $hash = shift;
my $indent = shift || '';
foreach my $k (sort {$a <=> $b} keys %{$hash}) {
my $v = $hash->{$k};
if($v->{parentid} eq '-1'){
$options .= $v->{title} ."-parent\n";
}else{
$options .= $v->{title} . "," ;
}
if ($v->{children}){
$iter->($v->{children}, $indent . "");
}
}
chop($options);
$options .= "\n";
};
$iter->($hash);
return $options;
}
here it returns a string with comma separated but I need some kind of data structure so that I can able to find all children for each key (in the form of hash ref or array) like:
Parent-1 -> [child-1,child-2, first child of child-2, second child of child-2]
Parent-2
Parent-3
Parent-4
any one can help me out? thanks in advance.
If your only goal is to display the hash contents, you should use the Data::Dumper module. It can be used to print data structures of arbitrary complexity with a good format.
You may also find brian d foy's answer on checking key existence useful
The code for with code for walking a data structure and Data::Diver may give you all the help you need.