A simple thought about key-value pairs
Tags: perl, json, kvpairs
Published: 2021-09-03 23:54:17

---
title: A simple thought about key-value pairs
published: true
description: 
tags: perl, json, kvpairs
---
Very often, we wrote Web API that produce JSON output and also very often, we want to ensure that certain values in the output are produced as numerical literals (`42` instead of `"42"`) or as boolean literals (`true` or `false`). The only reliable way, imho, is to cast them when printing the JSON output, or when preparing the structure for that output -- that is basically as "late" as possible.

Since we also often produce JSON with `JSON` module, we may have a `TO_JSON` around that looks like this, with many inline type-coercion expression:

```perl
sub TO_JSON ($self) {
    return {
        # string
        "align" => "". self->align,

        # number
        "height" => 0+ $self->height,
        "width" => 0+ $self->width,

        # boolean
        "closed" => $self->is_closed ? \1 : \0
        "is_landscape" => ( $self->height < $self->width)  ? \1 : \0
        "fits" => ( ($self->height * $self->width) < LIMIT ) ? \1 : \0
    }
}
```

When the unavoidable fate comes and we include more and more attributes in the `TO_JSON`, such inline expressions becomes bulky and a bit tedious to look at... just imagine the situation of having a few dozens of inline ternary expressions not perfectly aligned and one of them being wrong. It would be nicer to make them look good and make those inline expressions look like some sort of annotation:

```perl
sub TO_JSON ($self) {
    return {
        "align" => json_str(self->align),

        "height" => json_num( $self->height ),
        "width" => json_num( $self->width ),

        "closed" => json_bool( $self->is_closed ),
        "is_landscape" => json_bool( $self->height < $self->width ),
        "fits" => json_bool( ($self->height * $self->width) < LIMIT ),
    }
}
```

These helper methods that does type coercion by themselfs are trivial:

```perl
sub json_str ($x) { "". $x }
sub json_num ($x) { 0 + $x }
sub json_bool ($x) { $x ? \1 : \0 }
```

Or, perhaps even better, we could annotate the attributes in groups, arranged by their expected type:

```perl
sub TO_JSON ($self) {
    return {
        kvpairs_json_str(
            "align"  => self->align,
        ),

        kvpairs_json_num(
            "height" => $self->height,
            "width"  => $self->width,
        ),

        kvpairs_json_bool(
            "closed" => $self->is_closed,
            "is_landscape" => ( $self->height < $self->width ),
            "fits" => ( ($self->height * $self->width) < LIMIT ),
        )
    }
}
```

Using the `pairmap` function from `List::Util`, the new helper methods looks also trivial:

```perl
use List::Util ('pairmap');

sub kvpairs_json_str (@kvlist) {
    pairmap { ($a, json_str($b)) } pairs @kvlist
}
sub kvpairs_json_num (@kvlist) {
    pairmap { ($a, json_num($b)) } pairs @kvlist
}
sub kvpairs_json_bool (@kvlist) {
    pairmap { ($a, json_bool($b)) } pairs @kvlist
}
```

Since for our purpose of printing object as JSON, keys can be ignored because they are always string literals directly written as part of our code. We only have to deal all the values which are every 2nd elements in `@kvlist`. Also, because the overall result of `TO_JSON` is a HashRef, the order of keys as they are written in the code is irrelevant and that allows us to arrange those key-value pairs in groups so overall it looks nicer and organized.

Meanwhile, we also often hide partial kvpairs given some condition because some attributes are optional and perhaps convey special meaning when they are part of the output. A typical example would be the the conventional "error" attribute in API responses.

```perl
my $response = {
    defined($o->error) ? (
        "error" => $o->error,
    ):(),

    ...
};
```

That's a ternary op with an ever weirder look. I wonder if this is better:

```perl
my $response = {
    kvpairs_without_undef(
        "error" => $o->error,
        ...
    ),
    ...
};
```

That is like marking a section of kvpairs as optional and exclude all pairs with `undef` value. The helper function `kvpairs_without_undef` can be done with the help of `pairgrep`:

```perl
use List::Util ('pairgrep');

sub kvpairs_without_undef (@kvlist) {
    pairgrep { defined($b) } @kvlist
}
```

This is probably doing a lot of copying and perhaps there are some performance enhancements to be done. But well, these are just thoughts, optimizing thoughts is even before the so called premature optimization.

(Perhaps that's fun though.)

----
Originally published at: https://gugod.org/2021/07/perl-simple-thoughts-kvpairs/