The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Valiant::JSON::JSONBuilder - Wraps a model with a JSON builder

SYNOPSIS

Given an object defined like this:

    package Local::Test::User;

    use Moo;

    has username => (is=>'ro');
    has active => (is=>'ro');
    has age => (is=>'ro');

Create an instance of the object and then use the JSONBuilder to render it:

    my $user = Local::Test::User->new(
      username => 'bob',
      active => 1,
      age => 42,
    );

    my $jb = Valiant::JSON::JSONBuilder->new(model=>$user);
    my $json = $jb->string('username')
      ->boolean('active')
      ->number('age')
      ->render_json;

    say $json;

Response is:

   "local_test_user" : {
      "username" : "bob",
      "age" : 42,
      "active" : true
   }

You can control value the top level field of the rendered JSON or remove it:

    my $jb = Valiant::JSON::JSONBuilder->new(model=>$user, namespace=>'');
    my $json = $jb->string('username')
      ->boolean('active')
      ->number('age')
      ->render_json;

    say $json;

Response is:

    {
        "username" : "bob",
        "age" : 42,
        "active" : true
    }

It can also work with a view object, see below for details.

DESCRIPTION

Serializing an object into a JSON string is a common task. This module provides a simple way to do that using a JSON builder pattern. It is intended to be used with Valiant but can be used with any object.

There's tons of different patterns for serializing objects into JSON. One common approach is to add a TO_JSON method to your object. This is a fine approach but it has a few drawbacks the biggest of which is that it forces you into a single serialization pattern for the object. This might be ok for using serialization for saving an object to a database but it is less than ideal for rendering an object into a JSON response for an API. For example you might want to render the object differently depending on the user's role or depending on the API version, or for any other reason I'm sure you can think of.

Another approach is to just write some bespoke code to turn your object into a perl data structure suitable for passing to a JSON encoder such as JSON::MaybeXS. This is also a fine approach but it can be a bit tedious to write and maintain. It's also easy to mess up handling of nested objects and arrays or in situations when you want to make sure a scalar is coerced into a string or a number. The purpose behind this module is to encapsulate some basic patterns for serializing objects into JSON and to make it easy to extend and customize. Although this works well with simple cases I think it really shines when you have a complex object with nested objects and arrays contained within it as well as when you need to inject extra fields and values into the JSON response that are not part of the object itself. You can even include other objects in the response and the API includes some basic conditional logic to smooth over very complex cases.

ATTRIBUTES

This class defines the follwoing attributes.

model

This is the object to be serialized. It can be any object but it has some extensions to play nice with objects that are using Valiant for validation.

This value should be either a blessed object or the scalar name of the object which refers to an attribute on the view object (which must then be provided, see below)

namespace

This is the top level field name to use when rendering the object. If not specified it will use the model_name of the object if it has one. If the object does not have a model_name then it will use the class name of the object. If you want to render the object without a top level field then set this to an empty string.

view

This is an optional view object. If provided we can use view attributes to provide models.

json_args

This is an optional hashref of options which is passed to the JSON::MaybeXS encoder.

METHODS

This class defines the following methods.

reset

Clears any previously defined JSON field and value definitions but preserves the model and related attributes. Useful if you want to reuse the same builder object to render representations of the same model, but I mostly used it for testing and saw no reason to mark it private.

json_true

json_false

Returns an object representing a JSON boolean value for serialization. Useful since Perl doesn't have a native boolean type.

render_perl

Returns a perl data structure representing the current defined JSON structure. Useful for testing but I see no reason to mark it private

render_json

A JSON encoded string representing the current defined JSON structure.

string

Renders an object attribute as a string.

    $jb->string('username', \%options);

The options hashref is optional and can contain the following keys:

name

The name of the field to render. Generally this is an attribute on the model object (or the model has the get_attribute_for_json method defined). You can use a non attribute value here if you need to inject a field that isn't in the model (for example a CSRF token) but if you do so you must prove a value option.

value

If provided use this value instead of the value of the attribute.

omit_undef

If true then the field will not be rendered if the value is undefined.

omit_empty

If true then the field will not be rendered if the value is an empty string. For this to work the model should support has_attribute_for_json or provide a method called has_$attribute

number

Same as "string" but coerces the value into a number instead. Same options supported

boolean

Same as "string" but coerces the value into a boolean instead. Same options supported. Coerces into s JSON boolean value (true/false) not a Perl boolean value (1/0). See "json_true" and "json_false".

Supports one additional option: coerce_undef which if true will coerce an undefined value into a false value. By default an undefined value will be rendered as a JSON null value unless omit_undef is true. Same thing if the value is not present, that will render as null unless omit_empty is true.

value

This returns the value of the current field directly. Useful when you want to render a simple array of values

    package Local::Test::List;

    use Moo;

    has numbers => (is=>'ro');

    my $list_of_numbers = Local::Test::List->new(numbers=>[1,2,3]);
    my $jb = Valiant::JSON::JSONBuilder->new(model=>$list_of_numbers);
    my $json = $jb->array([1,2,3], {namespace=>'numbers'}, sub {
      my ($jb, $item) = @_;
      $jb->value($item);
    })->render_json;

response is:

    {
      "local_test_list" : {
          "numbers" : [
            "1",
            "2",
            "3"
          ]
      }
    }

array

METHODS FOR OBJECTS

Although not required, if the model object provides these methods they can be used to customize how we render the object.

get_attribute_for_json

When requesting that a model provide a value for an attribute, if this method is defined it will be called with the attribute name and be expected to return the value to be used for the attribute. this can be useful if you want to do some custom processing on the value before it is rendered or if you want to create virtual attributs jsut for JSON.

If the method doesn't exist we expect to find a method on the object that matches the attribute name.

has_attribute_for_json

When requesting that a model provide a value for an attribute, if this method is defined it will be called with the attribute name and be expected to return a boolean value indicating if the attributes exists or not. If this method is not provided we expect to find a method on the object that matches "has_$attribute_name", which is the common convention on Moo and Moose objects. If neither method exists you will get a runtime error.

This method is called if you use the omit_empty option on a builder method so you need to remember to provide the correct model API to support checking an attribute exists.

USING WITH A VIEW

SEE ALSO

Valiant, JSON::MaybeXS

AUTHOR

See Valiant

COPYRIGHT & LICENSE

See Valiant