diff --git a/raster/r.category/Makefile b/raster/r.category/Makefile
index 74909c5f608..5fd448ab5c5 100644
--- a/raster/r.category/Makefile
+++ b/raster/r.category/Makefile
@@ -2,7 +2,7 @@ MODULE_TOPDIR = ../..
 
 PGM = r.category
 
-LIBES = $(RASTERLIB) $(GISLIB)
+LIBES = $(RASTERLIB) $(GISLIB) $(PARSONLIB)
 DEPENDENCIES = $(RASTERDEP) $(GISDEP)
 
 include $(MODULE_TOPDIR)/include/Make/Module.make
diff --git a/raster/r.category/local_proto.h b/raster/r.category/local_proto.h
index 05514fbb9ee..370f98cd406 100644
--- a/raster/r.category/local_proto.h
+++ b/raster/r.category/local_proto.h
@@ -18,13 +18,18 @@
 #ifndef __LOCAL_PROTO_H__
 #define __LOCAL_PROTO_H__
 
+#include <grass/parson.h>
+
+enum OutputFormat { PLAIN, JSON };
+
 /* cats.c */
 int get_cats(const char *, const char *);
 int next_cat(long *);
 
 /* main.c */
-int print_label(long);
-int print_d_label(double);
+void print_json(JSON_Value *);
+int print_label(long, enum OutputFormat, JSON_Array *);
+int print_d_label(double, enum OutputFormat, JSON_Array *);
 int scan_cats(const char *, long *, long *);
 int scan_vals(const char *, double *);
 
diff --git a/raster/r.category/main.c b/raster/r.category/main.c
index b526ba657e2..d0b945a9064 100644
--- a/raster/r.category/main.c
+++ b/raster/r.category/main.c
@@ -22,6 +22,7 @@
 #include <grass/gis.h>
 #include <grass/raster.h>
 #include <grass/glocale.h>
+#include <grass/parson.h>
 #include "local_proto.h"
 
 static struct Categories cats;
@@ -39,9 +40,13 @@ int main(int argc, char *argv[])
     int from_stdin = FALSE;
     struct GModule *module;
 
+    enum OutputFormat format;
+    JSON_Value *root_value;
+    JSON_Array *root_array;
+
     struct {
         struct Option *map, *fs, *cats, *vals, *raster, *file, *fmt_str,
-            *fmt_coeff;
+            *fmt_coeff, *format;
     } parm;
 
     G_gisinit(argv[0]);
@@ -103,9 +108,25 @@ int main(int argc, char *argv[])
     parm.fmt_coeff->description =
         _("Two pairs of category multiplier and offsets, for $1 and $2");
 
+    parm.format = G_define_standard_option(G_OPT_F_FORMAT);
+    parm.format->key = "output_format";
+    parm.format->guisection = _("Print");
+
     if (G_parser(argc, argv))
         exit(EXIT_FAILURE);
 
+    if (strcmp(parm.format->answer, "json") == 0) {
+        format = JSON;
+        root_value = json_value_init_array();
+        if (root_value == NULL) {
+            G_fatal_error(_("Failed to initialize JSON array. Out of memory?"));
+        }
+        root_array = json_array(root_value);
+    }
+    else {
+        format = PLAIN;
+    }
+
     name = parm.map->answer;
 
     fs = G_option_to_separator(parm.fs);
@@ -282,7 +303,10 @@ int main(int argc, char *argv[])
         if (map_type == CELL_TYPE) {
             get_cats(name, mapset);
             while (next_cat(&x))
-                print_label(x);
+                print_label(x, format, root_array);
+            if (format == JSON) {
+                print_json(root_value);
+            }
             exit(EXIT_SUCCESS);
         }
     }
@@ -300,7 +324,10 @@ int main(int argc, char *argv[])
             for (i = 0; parm.cats->answers[i]; i++) {
                 scan_cats(parm.cats->answers[i], &x, &y);
                 while (x <= y)
-                    print_label(x++);
+                    print_label(x++, format, root_array);
+            }
+            if (format == JSON) {
+                print_json(root_value);
             }
             exit(EXIT_SUCCESS);
         }
@@ -315,31 +342,76 @@ int main(int argc, char *argv[])
         }
     for (i = 0; parm.vals->answers[i]; i++) {
         scan_vals(parm.vals->answers[i], &dx);
-        print_d_label(dx);
+        print_d_label(dx, format, root_array);
+    }
+
+    if (format == JSON) {
+        print_json(root_value);
     }
+
     exit(EXIT_SUCCESS);
 }
 
-int print_label(long x)
+void print_json(JSON_Value *root_value)
+{
+    char *serialized_string = NULL;
+    serialized_string = json_serialize_to_string_pretty(root_value);
+    if (serialized_string == NULL) {
+        G_fatal_error(_("Failed to initialize pretty JSON string."));
+    }
+    puts(serialized_string);
+    json_free_serialized_string(serialized_string);
+    json_value_free(root_value);
+}
+
+int print_label(long x, enum OutputFormat format, JSON_Array *root_array)
 {
     char *label;
+    JSON_Value *category_value;
+    JSON_Object *category;
 
     G_squeeze(label = Rast_get_c_cat((CELL *)&x, &cats));
-    fprintf(stdout, "%ld%s%s\n", x, fs, label);
+
+    switch (format) {
+    case PLAIN:
+        fprintf(stdout, "%ld%s%s\n", x, fs, label);
+        break;
+    case JSON:
+        category_value = json_value_init_object();
+        category = json_object(category_value);
+        json_object_set_number(category, "category", x);
+        json_object_set_string(category, "description", label);
+        json_array_append_value(root_array, category_value);
+        break;
+    }
 
     return 0;
 }
 
-int print_d_label(double x)
+int print_d_label(double x, enum OutputFormat format, JSON_Array *root_array)
 {
     char *label, tmp[40];
     DCELL dtmp;
+    JSON_Value *category_value;
+    JSON_Object *category;
 
     dtmp = x;
     G_squeeze(label = Rast_get_d_cat(&dtmp, &cats));
-    sprintf(tmp, "%.10f", x);
-    G_trim_decimal(tmp);
-    fprintf(stdout, "%s%s%s\n", tmp, fs, label);
+
+    switch (format) {
+    case PLAIN:
+        sprintf(tmp, "%.10f", x);
+        G_trim_decimal(tmp);
+        fprintf(stdout, "%s%s%s\n", tmp, fs, label);
+        break;
+    case JSON:
+        category_value = json_value_init_object();
+        category = json_object(category_value);
+        json_object_set_number(category, "category", x);
+        json_object_set_string(category, "description", label);
+        json_array_append_value(root_array, category_value);
+        break;
+    }
 
     return 0;
 }
diff --git a/raster/r.category/r.category.html b/raster/r.category/r.category.html
index 9a10e63779c..4dba832a98c 100644
--- a/raster/r.category/r.category.html
+++ b/raster/r.category/r.category.html
@@ -147,6 +147,26 @@ <h3>Printing categories</h3>
 as the character separating the category values from the category
 values in the output.
 
+<p>
+<div class="code"><pre>
+r.category map=landclass96 cats=3,4 output_format=json
+</pre></div>
+
+generates the following JSON output:
+
+<div class="code"><pre>
+[
+    {
+        "category": 3,
+        "description": "herbaceous"
+    },
+    {
+        "category": 4,
+        "description": "shrubland"
+    }
+]
+</pre></div>
+
 <h3>Adding categories</h3>
 
 Example for defining new category labels, using a colon as separator:
diff --git a/raster/r.category/test_rcategory_doctest.txt b/raster/r.category/test_rcategory_doctest.txt
index 9276c3e9e62..b8424bbb253 100644
--- a/raster/r.category/test_rcategory_doctest.txt
+++ b/raster/r.category/test_rcategory_doctest.txt
@@ -224,6 +224,22 @@ Some of these commands should not work and return 1.
 <BLANKLINE>
 
 
+JSON Output
+===========
+>>> print(read_command('r.category', map='test', output_format='json'))
+[
+    {
+        "category": 1,
+        "description": "trees, very green"
+    },
+    {
+        "category": 2,
+        "description": "water, very deep"
+    }
+]
+<BLANKLINE>
+
+
 Clean the results
 =================