7

I'm trying Hash::Ordered instead of Tie::IxHash, because it seems to be faster.

While Tie::IxHash is working fine, I struggle with some problems with Hash::Ordered. The point is to have the hashes ordered (which are usually random in Perl).

use Hash::Ordered;
use JSON::XS;
use Data::Dumper;

use strict;
use warnings;

my $json = JSON::XS->new;

my $oh = Hash::Ordered->new;
$oh->push('result' => { 'counter' => "123" }, 'number' => { 'num' => '55' });

my @r = $oh->as_list;

$json->pretty(1);
my $jsondata = $json->encode(\@r);
print Dumper $jsondata;

The result is odd:

 [
   "result",
   {
      "counter" : "123"
   },
   "number",
   {
      "num" : "55"
   }
]

Here is the working example with Tie::IxHash, I try to get the same results with Hash::Ordered.

use Data::Dumper;
use Tie::IxHash;
use JSON::XS;

use strict;
use warnings;

my $json = JSON::XS->new;

my %h;
tie(%h, 'Tie::IxHash', result => { counter => "123" }, number => { num => '55' });

  $json->pretty(1);
  my $pretty_json = $json->encode(\%h);

  print Dumper $pretty_json;

Output

{
   "result" : {
      "counter" : "123"
   },
   "number" : {
      "num" : "55"
   }
}
Borodin
  • 126,100
  • 9
  • 70
  • 144
user3606329
  • 2,405
  • 1
  • 16
  • 28
  • I don't know if it is related, but there seems to be cases where `JSON::XS` does not handle tied variables: [Strange behavior of a tied hash in perl, when asking for an arrayref](http://stackoverflow.com/questions/33738849/strange-behavior-of-a-tied-hash-in-perl-when-asking-for-an-arrayref) – Håkon Hægland Jun 25 '16 at 13:16

3 Answers3

4

The object-oriented interface of Hash::Ordered is much faster that the tied interface, but some utilities (like $json->encode) require a real hash reference

The way to get the best of both worlds is to tie a hash for use with those utilities, and use tied to extract the underlying Hash::Ordered object so that you can use the faster method calls to manipulate it

This short program demonstrates. The only slow part of this code is when the hash is passed to encode to be translated to JSON. The push call doesn't use the tied interface and remains fast

use strict;
use warnings;

use Hash::Ordered;
use JSON::XS;

my $json = JSON::XS->new->pretty;

tie my %h, 'Hash::Ordered';
my $oh = tied %h;
$oh->push( result => { counter => 123 }, number => { num => 55 } );

print $json->encode(\%h), "\n";

output

{
   "result" : {
      "counter" : 123
   },
   "number" : {
      "num" : 55
   }
}
Borodin
  • 126,100
  • 9
  • 70
  • 144
  • Great example. I have encountered one problem. Run it like this: $oh->push( result => { counter => 123, z => 5, s => 8 }, number => { num => 55, p => 123 }); Ths will loss the order for the sub-elements inside the hash. {"result": "counter":123,"s":8,"z":5},"number":{"p":123,"num":55}} How would I keep the order in this example? – user3606329 Jun 26 '16 at 15:52
  • I have just added the solution in an answer. Feel free to edit or remove my answer, if there is an easier way. – user3606329 Jun 26 '16 at 17:09
3

Use the Hash::Ordered tied interface:

my $json = JSON::XS->new;

tie my %hash, "Hash::Ordered";

$hash{'result'} = { 'counter' => "123" };
$hash{'number1'} = { 'num' => '1' };
$hash{'number2'} = { 'num' => '2' };
$hash{'number3'} = { 'num' => '3' };
$hash{'last'} = { 'num' => 'last' };

$json->pretty(1);
my $jsondata = $json->encode(\%hash);

And the JSON data you get is:

{
   "result" : {
      "counter" : "123"
   },
   "number1" : {
      "num" : "1"
   },
   "number2" : {
      "num" : "2"
   },
   "number3" : {
      "num" : "3"
   },
   "last" : {
      "num" : "last"
   }
}
Miguel Prz
  • 13,718
  • 29
  • 42
  • Thanks for your reply! I think I've tried this before! When I use a hash for 'as_list' the order is lost. If you 'print Dumper %r' after $oh->as_list, it moves the JSON-Data "result" to the bottom. In other words the output is { "number" : { "num" : "55" }, "result" : { "counter" : "123" } }. It seems only an array is working with Hash::Ordered? – user3606329 Jun 25 '16 at 12:59
2

The examples above work fine, but for multidimensional hashes there is an additional step needed to keep the order.

use Hash::Ordered;
use JSON::XS;
use Data::Dumper;

use strict;
use warnings;   

sub ordered_hash_ref {
    tie my %hash, 'Hash::Ordered';
    my $oh = tied %hash;
    $oh->push(@_);
    return \%hash;
};

my $json = JSON::XS->new->pretty;
tie my %h, 'Hash::Ordered';
my $oh = tied %h;

$oh->push(result => ordered_hash_ref(counter => 123, z => 5, s => 8),  address => ordered_hash_ref(Vorname => 'Max', 
Nachname => 'Mustermann', Strasse => 'Feldweg', Hausnummer => 45));

my $pretty = $json->encode(\%h);
print Dumper $pretty;

Output

{
   "result" : {
      "counter" : 123,
      "z" : 5,
      "s" : 8
   },
   "address" : {
      "Vorname" : "Max",
      "Nachname" : "Mustermann",
      "Strasse" : "Feldweg",
      "Hausnummer" : 45
   }
}
user3606329
  • 2,405
  • 1
  • 16
  • 28