Skip to content

Data format

When a model is reached remotely, four things travel between the application and the server: a model's metadata, the request of a visual, its data, and the distinct values of a dimension. DataSerializer (EasyBI.Core) writes and reads them as plain JSON, so that the server can be written in .NET or in any other language. The transport is yours (see Your own data source).

What Write Read
Metadata Serialize(ModelMetadata) DeserializeMetadata(json)
Request of a visual Serialize(VisualDataRequest) DeserializeRequest(json)
Data of a visual Serialize(VisualData) DeserializeData(json)
Distinct values asked by a filter Serialize(DistinctValuesRequest) DeserializeValuesRequest(json)
Distinct values Serialize(DistinctValues) DeserializeValues(json, dataType)

DataSerializer.Options reads and writes the same JSON with JsonSerializer directly: on a stream, with ASP.NET Core's Results.Json, HttpClient's ReadFromJsonAsync...

Metadata

{
  "name": "sales",
  "caption": "Sales",
  "fields": [
    { "name": "Date", "caption": "Date", "kind": "Dimension", "dataType": "Date", "isCalculated": false },
    { "name": "Region", "caption": "Region", "kind": "Dimension", "dataType": "Text", "isCalculated": false },
    { "name": "Amount", "caption": "Revenue", "kind": "Measure", "dataType": "Number", "defaultAggregation": "Sum", "format": "#,##0 €", "isCalculated": false },
    { "name": "MarginRate", "caption": "Margin rate", "kind": "Measure", "dataType": "Number", "format": "P1", "isCalculated": true }
  ]
}

Captions are in one language: a server that translates them returns them in the language the application asks for (the sample's server follows the request's Accept-Language).

Request

{
  "visual": {
    "model": "sales",
    "type": "Bar",
    "axis": [ { "field": "Date", "grain": "Quarter" } ],
    "legend": { "field": "Channel", "grain": "None" },
    "values": [ { "measure": "Amount" }, { "measure": "WebRevenue" } ],
    "filters": [ { "field": "Region", "operator": "In", "values": [ "North" ], "grain": "None" } ]
  },
  "measures": [ { "name": "WebRevenue", "caption": "Web revenue", "model": "sales", "formula": "SUM([Amount]; [Channel] = \"Web\")" } ],
  "hasTooltips": true,
  "today": "2026-10-27",
  "uiCulture": "en-US",
  "formatCulture": "en-US"
}

visual is a visual as in the report format, with the filters of its page and the selections of other visuals already applied. measures are the end user's calculated measures it uses. today is the user's day (yyyy-MM-dd, no time zone), on which relative periods and comparisons with a year earlier are computed. The cultures are omitted for the server's own.

Data

{
  "result": {
    "columns": [
      { "key": "Region", "caption": "Region", "role": "Dimension", "dataType": "Text" },
      { "key": "Date.Year", "caption": "Date (Year)", "role": "Dimension", "dataType": "Text" },
      { "key": "Amount", "caption": "Revenue", "role": "Measure", "dataType": "Number", "format": "#,##0 €" }
    ],
    "rows": [
      [ "North", { "grain": "Year", "key": 2023 }, 1258638.78 ],
      [ "North", { "grain": "Year", "key": 2024 }, 1385525.22 ]
    ]
  },
  "matrix": {
    "rowCaptions": [ "Region" ],
    "columnCaption": "Date (Year)",
    "rows": [ { "values": [ "North" ], "labels": [ "North" ] }, { "values": [ "South" ], "labels": [ "South" ] } ],
    "columns": [ { "values": [ { "grain": "Year", "key": 2023 } ], "labels": [ "2023" ] }, ... ],
    "measures": [ { "key": "Amount", "caption": "Revenue", "format": "#,##0 €" } ],
    "cells": [ [ [ 1258638.78 ], [ 1385525.22 ] ], [ [ 1064750.5 ], [ 1059973.01 ] ] ],
    "rowTotals": [ [ 2644164.0 ], [ 2124723.51 ] ],
    "columnTotals": [ [ 2323389.28 ], [ 2445498.23 ] ],
    "grandTotals": [ 4768887.51 ]
  }
}

result has one row per group, with one value per column. matrix is only there for a matrix: its cells are indexed cells[row][column][measure] (see MatrixData).

Values

Value JSON
Missing null
Text, enum "North"
Number 1258.5 ("NaN" for a value that is not a number)
Boolean true
Date "2024-03-10", or "2024-03-10T14:30:00.0000000" with its time
Period of a date grain (year, quarter, month) { "grain": "Month", "key": 202403 }: the key is 2024, 20241 (Q1) or 202403

The column's dataType tells a date from a text: in a Date column, "2024-03-10" is read as a date.

Distinct values

A filter asks for the values of a dimension, at most limit (10,000 by default); when the list was truncated, a search asks for the values containing a text, ignoring case:

{ "field": "Customer", "search": "dupont", "limit": 10000 }

The answer lists the values in order, read with the type of their dimension (Metadata.GetField(field).DataType), and tells whether there were more than the limit:

{ "values": [ "Alice Dupont", "Hugo Dupont", null ], "isTruncated": false }

Compatibility

Like the report format, this format only grows with optional properties: a reader ignores properties it does not know and uses defaults for the missing ones, so that an application and its server can be updated one after the other.