'Render the pivot table data as JSON-serializable dict
@param layer: the layer
@param maxrows: maximum number of rows (None for all)
@param maxcols: maximum number of columns (None for all)
@param least: render the least n rows/columns rather than
the top n (with maxrows/maxcols)
labels: {
layer:
rows:
cols:
total:
method: <aggregation method>,
cells: [rows[cols]],
rows: [rows[index, value, label, total]],
cols: [cols[index, value, label, total]],
total: <grand total>,
filter: [rows selector, cols selector]'

def json(self, maxrows=None, maxcols=None):

    rfields = self.rfields
    resource = self.resource
    T = current.T
    OTHER = '__other__'
    rows_dim = self.rows
    cols_dim = self.cols
    orows = []
    rappend = orows.append
    ocols = []
    cappend = ocols.append
    ocells = []
    lookups = {}
    facts = self.facts
    if (not self.empty):
        row_repr = self._represent_method(rows_dim)
        col_repr = self._represent_method(cols_dim)
        others = s3_unicode(T('Others'))
        layers = [fact.layer for fact in facts]
        least = (facts[0].method == 'min')
        irows = self.row
        rows = []
        rtail = (None, None)
        for i in xrange(self.numrows):
            irow = irows[i]
            totals = [irow[layer] for layer in layers]
            sort_total = totals[0]
            header = {'value': irow.value, 'text': (irow.text if ('text' in irow) else row_repr(irow.value))}
            rows.append((i, sort_total, totals, header))
        if (maxrows is not None):
            rtail = self._tail(rows, maxrows, least=least, facts=facts)
        self._sortdim(rows, rfields[rows_dim])
        if (rtail[1] is not None):
            values = [irows[i]['value'] for i in rtail[0]]
            rows.append((OTHER, rtail[1], rtail[2], {'value': values, 'text': others}))
        icols = self.col
        cols = []
        ctail = (None, None)
        for i in xrange(self.numcols):
            icol = icols[i]
            totals = [icol[layer] for layer in layers]
            sort_total = totals[0]
            header = {'value': icol.value, 'text': (icol.text if ('text' in icol) else col_repr(icol.value))}
            cols.append((i, sort_total, totals, header))
        if (maxcols is not None):
            ctail = self._tail(cols, maxcols, least=least, facts=facts)
        self._sortdim(cols, rfields[cols_dim])
        if (ctail[1] is not None):
            values = [icols[i]['value'] for i in ctail[0]]
            cols.append((OTHER, ctail[1], ctail[2], {'value': values, 'text': others}))
        rothers = (rtail[0] or set())
        cothers = (ctail[0] or set())
        icell = self.cell
        cells = {}
        for i in xrange(self.numrows):
            irow = icell[i]
            ridx = ((i, OTHER) if (rothers and (i in rothers)) else (i,))
            for j in xrange(self.numcols):
                cell = irow[j]
                cidx = ((j, OTHER) if (cothers and (j in cothers)) else (j,))
                cell_records = cell['records']
                for (layer_index, layer) in enumerate(layers):
                    items = cell[layer]
                    if isinstance(items, list):
                        value = len(items)
                    else:
                        value = items
                    for ri in ridx:
                        if (ri not in cells):
                            orow = cells[ri] = {}
                        else:
                            orow = cells[ri]
                        for ci in cidx:
                            if (ci not in orow):
                                ocell = orow[ci] = {'values': [], 'items': [], 'records': []}
                            else:
                                ocell = orow[ci]
                            if (layer_index == 0):
                                ocell['records'].extend(cell_records)
                            value_array = ocell['values']
                            items_array = ocell['items']
                            if (len(value_array) <= layer_index):
                                value_array.append(value)
                                items_array.append(items)
                            else:
                                ovalue = value_array[layer_index]
                                oitems = items_array[layer_index]
                                if isinstance(ovalue, list):
                                    ovalue.append(value)
                                    oitems.append(items)
                                else:
                                    value_array[layer_index] = [ovalue, value]
                                    items_array[layer_index] = [oitems, items]
        represents = self._represents(layers)
        value_maps = {}
        add_columns = True
        for (rindex, rtotal, rtotals, rtitle) in rows:
            orow = []
            rval = rtitle['value']
            if ((rindex == OTHER) and isinstance(rval, list)):
                rval = ','.join((s3_unicode(v) for v in rval))
            elif (rval is not None):
                rval = s3_unicode(rval)
            rappend((rindex, (rindex in rothers), rtotals, rval, rtitle['text']))
            for (cindex, ctotal, ctotals, ctitle) in cols:
                cell = cells[rindex][cindex]
                value_array = cell['values']
                items_array = cell['items']
                ocell = {'items': [], 'values': [], 'keys': []}
                for (layer_index, fact) in enumerate(facts):
                    (selector, method) = fact.layer
                    if (selector not in lookups):
                        lookup = lookups[selector] = {}
                    else:
                        lookup = lookups[selector]
                    if (selector not in value_maps):
                        value_map = value_maps[selector] = {}
                    else:
                        value_map = value_maps[selector]
                    value = value_array[layer_index]
                    if (type(value) is list):
                        value = fact.aggregate_totals(value)
                    ocell['values'].append(value)
                    (has_fk, _repr) = represents[selector]
                    rfield = self.rfields[selector]
                    items = items_array[layer_index]
                    okeys = None
                    if (method in ('count', 'list')):
                        keys = []
                        for record_id in cell['records']:
                            record = self.records[record_id]
                            try:
                                fvalue = record[rfield.colname]
                            except AttributeError:
                                continue
                            if (fvalue is None):
                                continue
                            if (type(fvalue) is not list):
                                fvalue = [fvalue]
                            for v in fvalue:
                                if (v is None):
                                    continue
                                if has_fk:
                                    if (v not in keys):
                                        keys.append(v)
                                    if (v not in lookup):
                                        lookup[v] = _repr(v)
                                elif (v not in value_map):
                                    next_id = len(value_map)
                                    value_map[v] = next_id
                                    keys.append(next_id)
                                    lookup[next_id] = _repr(v)
                                else:
                                    prev_id = value_map[v]
                                    if (prev_id not in keys):
                                        keys.append(prev_id)
                        keys.sort(key=(lambda i: lookup[i]))
                        if (method == 'list'):
                            items = [lookup[key] for key in keys if (key in lookup)]
                        else:
                            okeys = keys
                    ocell['items'].append(items)
                    ocell['keys'].append(okeys)
                orow.append(ocell)
                if add_columns:
                    cval = ctitle['value']
                    if ((cindex == OTHER) and isinstance(cval, list)):
                        cval = ','.join((s3_unicode(v) for v in cval))
                    elif (cval is not None):
                        cval = s3_unicode(cval)
                    cappend((cindex, (cindex in cothers), ctotals, cval, ctitle['text']))
            add_columns = False
            ocells.append(orow)
    report_options = resource.get_config('report_options', {})
    if report_options:
        fact_options = report_options.get('fact')
    else:
        fact_options = ()
    fact_data = []
    fact_labels = []
    for fact in facts:
        rfield = rfields[fact.selector]
        fact_label = str(fact.get_label(rfield, fact_options))
        fact_data.append((fact.selector, fact.method, fact_label))
        fact_labels.append(fact_label)
    get_label = S3PivotTableFact._get_field_label
    if rows_dim:
        rows_label = str(get_label(rfields[rows_dim], report_options.get('rows')))
    else:
        rows_label = ''
    if cols_dim:
        cols_label = str(get_label(rfields[cols_dim], report_options.get('cols')))
    else:
        cols_label = ''
    labels = {'total': str(T('Total')), 'none': str(current.messages['NONE']), 'per': str(T('per')), 'breakdown': str(T('Breakdown')), 'layer': '    /    '.join(fact_labels), 'rows': rows_label, 'cols': cols_label}
    output = {'rows': orows, 'cols': ocols, 'facts': fact_data, 'cells': ocells, 'lookups': lookups, 'total': self._totals(self.totals, [fact]), 'nodata': (None if (not self.empty) else str(T('No    data    available'))), 'labels': labels}
    prefix = resource.prefix_selector
    output['filter'] = ((prefix(rows_dim) if rows_dim else None), (prefix(cols_dim) if cols_dim else None))
    return output
