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 avalue
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 calledhas_$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
AUTHOR
See Valiant
COPYRIGHT & LICENSE
See Valiant