0

Based on this post, I am trying to understand how sort_by works in JSON::PP.

When I run this code

#!/usr/bin/perl
use strict;
use warnings;
use JSON::PP;
use Data::Dumper qw(Dumper);

my $h = {
    22 => { title => "c", name => "d" },
    1  => { title => "1", name => "a" },
    10 => { title => "a", name => "c" },
    5  => { title => "b", name => "b" },
};

my $sorter = sub {
    # See what's going on.
    print "$JSON::PP::a cmp $JSON::PP::b\n";
    print Dumper(\@_, $_);
    <STDIN>; # press return to continue

    $JSON::PP::a cmp $JSON::PP::b
};

my $js = JSON::PP->new;
my $output = $js->sort_by($sorter)->encode($h);
print $output . "\n";

it first sorts the inner keys, and then the outer keys, which determines the final order in the JSON string.

Right now it outputs

{"1":{"name":"a","title":"1"},"10":{"name":"c","title":"a"},"22":{"name":"d","title":"c"},"5":{"name":"b","title":"b"}}

and what I would like to end up with is that it is sorted by title ie.

{"1":{"name":"a","title":"1"},"5":{"name":"b","title":"b"}"10",{"name":"c","title":"a"},"22":{"name":"d","title":"c"}}

I suppose the first problem is to disable the last outter key sort?

Then how do I get hold of the value of title? When the algorithm runs, $JSON::PP::a and $JSON::PP::b contains the value name and title from the same hash.

This I can't figure out. Can anyone explain this, and/or help me write this algorithm?

Community
  • 1
  • 1
Sandra Schlichting
  • 25,050
  • 33
  • 110
  • 162
  • 1
    Sidenote: I think you should probably be sending Arrays instead of Objects (well, Arrays of Objects) if order actually matters and it's not just a cosmetic concern. These samples have a lot of code smell. :| – Ashley Jun 08 '11 at 20:45
  • @Ashley : The order is very important. How would encode my object into an array of objects? – Sandra Schlichting Jun 08 '11 at 21:50
  • 1
    You would make a reference to an array containing the hashes (and probably move the `keys/ids` into the hashes). You might have to rearrange your client-side handling but it would make more sense than relying on Object/pair order anyway, even in JS. You said you wanted things sorted by "title" but your desired output seems sorted by "name" or the `keys`. Perhaps a new question is in order: with the input on the server and the client-side treatment of the data(?). – Ashley Jun 08 '11 at 22:16
  • To quote [json.org](http://www.json.org/): "An object is an **unordered** set of name/value pairs." (emphasis mine) If ordering is important, then you need an array, not an object (the JSON equivalent of a hash), as Ashley said. JSON::PP provides `sort_by` just to make the output more easily human-readable, and to ensure consistent output to enable caching, etc. – cjm Jun 09 '11 at 01:03

2 Answers2

2

You can't, or at least not easily. The function you give to sort_by only has access to the keys being sorted. In order to do what you want you'd need to have access to the values associated with those keys (or, more likely, the hashref to which the keys belong, so you could look up the values yourself). That seems like it would be a useful enhancement; you might submit a feature request.

If your data structure is simple enough (which your example seems to be), you could keep a reference to the hash yourself. In order to do that, you have to be able to distinguish the keys in the inner hashes from the keys in the outer hashes (so you know which hash is being sorted, and thus what kind of comparison to do).

my $sorter = sub {
    if ($JSON::PP::a =~ /^\d+$/) {
      return $h->{$JSON::PP::a}{title} cmp $h->{$JSON::PP::b}{title};
    }
    return $JSON::PP::a cmp $JSON::PP::b
};
cjm
  • 61,471
  • 9
  • 126
  • 175
1

Try something like:

my $sorter = sub {
    my $h = $_[0];

    # simple check for if we are too deep
    # just sort by keys in that case
    return $JSON::PP::a cmp $JSON::PP::b
        if ref($h->{$JSON::PP::a}) ne 'HASH';

    # sort by titles value
    return $h->{$JSON::PP::a}{title} cmp $h->{$JSON::PP::b}{title};
};

$_[0] is the hash currently being sorted, altho this does not seem to be documented (so possibly unreliable).

It works for this case, but more complex structures will have problems since the depth check is so simple. A check on the key type like cjm did is better if you are sure that there are no deeper keys of that type. Or a combination, like:

my $sorter = sub {
    my $h = $_[0];

    # just sort by the keys
    return $JSON::PP::a cmp $JSON::PP::b
        unless $JSON::PP::a =~ /^\d+\z/
            && $JSON::PP::b =~ /^\d+\z/
            && ref($h->{$JSON::PP::a}) eq 'HASH'
            && ref($h->{$JSON::PP::b}) eq 'HASH';

    # sort by titles
    return $h->{$JSON::PP::a}{title} cmp $h->{$JSON::PP::b}{title};
};
Qtax
  • 33,241
  • 9
  • 83
  • 121
  • 1
    "$_[0] is the hash currently being sorted" is actually two levels of undocumented behavior. First, that Perl's `sort` doesn't change `@_` when the comparison subroutine doesn't have a prototype. Second, that JSON::PP passed the hash as the first parameter to its `_sort` subroutine. I'd hesitate to depend on all that. – cjm Jun 08 '11 at 21:26
  • You write `cmd` instead of `cmp`. I can understand why =) – Sandra Schlichting Jun 08 '11 at 21:47