diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
index 160319bffa..708f560826 100644
--- a/.github/workflows/unit-tests.yml
+++ b/.github/workflows/unit-tests.yml
@@ -26,8 +26,6 @@ jobs:
libencode-perl \
libgd-perl \
libhtml-parser-perl \
- libjson-perl \
- libjson-xs-perl \
liblocale-maketext-lexicon-perl \
libmojolicious-perl \
libtest2-suite-perl \
diff --git a/conf/pg_config.dist.yml b/conf/pg_config.dist.yml
index 9fedd6e308..245756c485 100644
--- a/conf/pg_config.dist.yml
+++ b/conf/pg_config.dist.yml
@@ -248,7 +248,7 @@ modules:
- ['Tie::IxHash']
- ['Locale::Maketext']
- ['WeBWorK::PG::Localize']
- - [JSON]
+ - ['Mojo::JSON']
- ['Class::Tiny']
- ['IO::Handle']
- ['Rserve']
diff --git a/cpanfile b/cpanfile
index ca6cbe1f03..1b553cbdb2 100644
--- a/cpanfile
+++ b/cpanfile
@@ -14,8 +14,7 @@ on runtime => sub {
requires 'GD';
requires 'HTML::Entities';
requires 'HTML::Parser';
- requires 'JSON';
- requires 'JSON::XS';
+ requires 'Mojo::JSON';
requires 'Locale::Maketext';
requires 'Locale::Maketext::Lexicon';
requires 'Mojolicious';
diff --git a/docker/pg.Dockerfile b/docker/pg.Dockerfile
index 20c6c60062..ccd0a13d67 100644
--- a/docker/pg.Dockerfile
+++ b/docker/pg.Dockerfile
@@ -18,8 +18,6 @@ RUN apt-get update \
libencode-perl \
libgd-perl \
libhtml-parser-perl \
- libjson-perl \
- libjson-xs-perl \
liblocale-maketext-lexicon-perl \
libmojolicious-perl \
libtest2-suite-perl \
diff --git a/lib/AnswerHash.pm b/lib/AnswerHash.pm
index 0f6efeec36..012fe4aba2 100755
--- a/lib/AnswerHash.pm
+++ b/lib/AnswerHash.pm
@@ -126,8 +126,8 @@ The answer hash class is guaranteed to contain the following instance variables:
package AnswerHash;
use Exporter;
-use PGUtil qw(not_null pretty_print);
-use JSON;
+use PGUtil qw(not_null pretty_print);
+use Mojo::JSON qw(encode_json);
# initialization fields
my %fields = (
@@ -269,7 +269,7 @@ sub stringify_hash {
my $ref = ref($self->{$key});
next if !$ref;
if ($ref eq "HASH" or $ref eq "ARRAY") {
- $self->{$key} = JSON->new->utf8->allow_unknown->convert_blessed->allow_blessed->encode($self->{$key});
+ $self->{$key} = encode_json($self->{$key});
} else {
$self->{$key} = "$self->{$key}";
}
diff --git a/lib/DragNDrop.pm b/lib/DragNDrop.pm
index 62a0ea4ec6..f7c5fdde31 100644
--- a/lib/DragNDrop.pm
+++ b/lib/DragNDrop.pm
@@ -126,7 +126,7 @@ package DragNDrop;
use strict;
use warnings;
-use JSON;
+use Mojo::JSON qw(encode_json);
use PGcore;
@@ -154,8 +154,8 @@ sub HTML {
my $self = shift;
my $out = qq{
new->encode($self->{itemList})) . '"';
- $out .= ' data-default-state="' . PGcore::encode_pg_and_html(JSON->new->encode($self->{defaultBuckets})) . '"';
+ $out .= ' data-item-list="' . PGcore::encode_pg_and_html(encode_json($self->{itemList})) . '"';
+ $out .= ' data-default-state="' . PGcore::encode_pg_and_html(encode_json($self->{defaultBuckets})) . '"';
$out .= qq{ data-remove-button-text="$self->{removeButtonText}"};
$out .= qq{ data-label-format="$self->{bucketLabelFormat}"} if $self->{bucketLabelFormat};
$out .= '>';
diff --git a/lib/Value.pm b/lib/Value.pm
index 64b3d955f5..8c23f9ead0 100644
--- a/lib/Value.pm
+++ b/lib/Value.pm
@@ -1033,7 +1033,7 @@ sub string {
return $open . join($comma, @coords) . $close;
}
-# This is called by JSON->encode when convert_blessed is true to convert this value object into a string.
+# This is called by Mojo::JSON::encode_json when convert_blessed is true to convert this value object into a string.
sub TO_JSON {
return shift->string;
}
diff --git a/lib/WeBWorK/PG/IO.pm b/lib/WeBWorK/PG/IO.pm
index d561472d39..49c889ac2c 100644
--- a/lib/WeBWorK/PG/IO.pm
+++ b/lib/WeBWorK/PG/IO.pm
@@ -32,7 +32,7 @@ use warnings;
use utf8;
use Encode qw(encode decode);
-use JSON qw(decode_json);
+use Mojo::JSON qw(decode_json);
use File::Spec::Functions qw(canonpath);
use File::Find qw(finddepth);
diff --git a/macros/PG.pl b/macros/PG.pl
index ccba3338ea..da3bc5e686 100644
--- a/macros/PG.pl
+++ b/macros/PG.pl
@@ -1029,7 +1029,7 @@ sub ENDDOCUMENT {
id => $name,
value => $inputs_ref->{$name} // '',
scalar(keys %$mq_part_opts)
- ? (data => { mq_opts => JSON->new->encode($mq_part_opts) })
+ ? (data => { mq_opts => Mojo::JSON::encode_json($mq_part_opts) })
: ''
)->to_string
);
@@ -1257,7 +1257,7 @@ sub ENDDOCUMENT {
}
)->to_string
),
- answer_labels => JSON->new->encode($response_obj->{response_order}),
+ answer_labels => Mojo::JSON::encode_json($response_obj->{response_order}),
bs_toggle => 'popover',
bs_trigger => 'click',
bs_placement => $showCorrectOnly ? 'right' : 'bottom',
diff --git a/macros/graph/LiveGraphics3D.pl b/macros/graph/LiveGraphics3D.pl
index 2d7fa3d24d..c7dd96cdfc 100644
--- a/macros/graph/LiveGraphics3D.pl
+++ b/macros/graph/LiveGraphics3D.pl
@@ -150,7 +150,7 @@ sub LiveGraphics3D {
'div',
class => 'live-graphics-3d-container',
style => "width:${w}px;height:${h}px;border:1px solid black;",
- data_options => JSON->new->encode({
+ data_options => Mojo::JSON::encode_json({
width => $w - 2,
height => $h - 2,
maxTicks => $options{max_ticks},
diff --git a/macros/graph/parserGraphTool.pl b/macros/graph/parserGraphTool.pl
index 44e29acdc3..7408ca7cab 100644
--- a/macros/graph/parserGraphTool.pl
+++ b/macros/graph/parserGraphTool.pl
@@ -291,7 +291,7 @@ =head1 OPTIONS
For example the following value for C will give the same result for the
JavaScript graph as the default values for the options above:
- JSXGraphOptions => JSON->new->encode({
+ JSXGraphOptions => Mojo::JSON::encode_json({
boundingBox => [-10, 10, 10, -10],
defaultAxes => {
x => { ticks => { ticksDistance => 2, minorTicks => 1} },
@@ -2300,7 +2300,7 @@ sub ANS_NAME {
sub constructJSXGraphOptions {
my $self = shift;
return if defined($self->{JSXGraphOptions});
- $self->{JSXGraphOptions} = JSON->new->encode({
+ $self->{JSXGraphOptions} = Mojo::JSON::encode_json({
boundingBox => $self->{bBox},
$self->{numberLine}
? (
diff --git a/macros/parsers/parserRadioMultiAnswer.pl b/macros/parsers/parserRadioMultiAnswer.pl
index 267d52a9ac..a8f4c0c09d 100644
--- a/macros/parsers/parserRadioMultiAnswer.pl
+++ b/macros/parsers/parserRadioMultiAnswer.pl
@@ -422,7 +422,7 @@ sub cmp {
$options->{insertElement} = $selected->first->parent;
my $contents = $selected->first->parent->at('div.radio-content[data-radio]');
if ($contents) {
- my $partNames = JSON->new->decode($contents->attr('data-part-names'));
+ my $partNames = Mojo::JSON::decode_json($contents->attr('data-part-names'));
for (@$partNames) {
my $ansRules = $contents->find(qq{[name="$_"]});
if (@$ansRules > 1) {
@@ -679,7 +679,7 @@ sub ans_rule {
class => 'radio-content',
data_radio => $radio_name,
data_index => $self->{values}[$i],
- data_part_names => JSON->new->encode(\@part_names),
+ data_part_names => Mojo::JSON::encode_json(\@part_names),
sprintf($data[$i][0] =~ s/%s\*/%s/gr, @part_rules)
),
PTX => sprintf($data[$i][0] =~ s/%s\*/%s/gr, @part_rules)