JsonDataReader
Namespace: Profitbase.Flow.Data.Analysis
Reads and flattens a JSON string as a sequence of rows. The JsonDataReader makes it easy to flatten and read JSON as a sequence of rows, or convert JSON to tables.
Implements IDataReader IDataRecord
Example 1 - Read JSON as a sequence of rows
This example shows how to read an array of JSON objects using the JsonDataReader, and get the value of each property using the Get[Type](...)
methods.
var json = """
[
{
"name": "John",
"age": 30,
"address": {
"city": "New York",
"street": "Broadway"
}
},
{
"name": "Jane",
"age": 33,
"address": {
"city": "Los Angeles",
"street": "Victory Boulevard"
}
}
]
""";
var jsonReader = new JsonDataReader(json);
while(jsonReader.Read())
{
// Get values by property index
string name = jsonReader.GetString(0);
int age = jsonReader.GetInt32(1);
// Get values by property name
string city = jsonReader.GetString("address_city"); // Nested object properties are separated by underscores.
string street = jsonReader.GetString("address_street");
}
Example 2 - Convert a JSON string to a DataTable
This example shows how to convert a JSON string to a DataTable using the JsonDataReader. The schema is automatically inferred based on the JSON string.
var json = """
[
{
"name": "John",
"age": 30,
"address": {
"city": "New York",
"street": "Broadway"
}
},
{
"name": "Jane",
"age": 33,
"address": {
"city": "Los Angeles",
"street": "Victory Boulevard"
}
}
]
""";
var jsonReader = new JsonDataReader(json);
DataTable dataTable = jsonReader.ToDataTable();
Working with schemas and data types
JSON only supports the following primitive data types: String
, Boolean
, Date
, and Number
. This differs from most programming languages and databases, which use more granular data types. As a result, explicit data type mappings are often needed when interfacing JSON with other technologies.
When loading JSON using the JsonDataReader, you have the option to explicitly specify the schema and data types to use, or let JsonDataReader infer the information based on the contents of the JSON document.
Inferred schema (No schema defined)
Data types
If no schema is defined, JsonDataReader will make a best effort attempt to infer the data types of each property based on their values. Data types are inferred as follows:
- Boolean values: The only valid boolean values in JSON are
true
andfalse
, so JsonDataReader will infer the data type for boolean properties automatically. - Numeric values: Based on the size and whether or not the number has decimals, JsonDataReader infers the data type as an
Int32
,Int64
,double
ordecimal
- in that order. - Strings values: When JsonDataReader encounters a string property, it checks whether or not the string has a
date
format. If the string has a valid date format, the data type will be eitherDateTime
ofDateTimeOffset
based on the precision of the date string. If the string does not have a valid date format, the data type will bestring
. - Date / DateTime values: Same as for string values (above).
Note
If a property value is null
, JsonDataReader cannot infer the data type of a property. To handle this case, you can implement the OnResolveType callback to specify the data type explicitly.
Column names
If no schema is defined, JsonDataReader will infer the column names based on the property names in the JSON document. Neste objects will be separated by underscores ("_").
The JSON
{"name": "John", "age": 35, "dateOfBirth": "1985-12-03"}
returns rows on the following format:
name | age | dateOfBirth |
---|---|---|
John | 35 | 1985-12-03 |
If you have a nested object, for example
{
"name": "John", "age": 35, "dateOfBirth": "1985-12-03",
"address": {
"city": "London",
"street": "Brick Lane"
}
}
the output will be:
name | age | dateOfBirth | address_city | address_street |
---|---|---|---|---|
John | 35 | 1985-12-03 | London | Brick Lane |
Explicitly configured schema
You can explicitly configure the schema for the JsonDataReader through the Schema
property of a JsonDataReader instance. This lets you map each JSON property name to a column name, and also specify the data type for each column.
When the schema and data types are specified explicitly, JsonDataReader will automatically convert the property values to the correct data type when parsing the JSON document.
Note
If you specify a schema using MapJsonProperty(...) as shown in the example below, JsonDataReader will ignore all other fields in the JSON document.
Example - Configure JsonDataReader schema
The following example shows how to configure the schema JsonDataReader should use. Note that the lower case JSON properties are mapped to Pascal-cased column names, and that the nested properties under address
are mapped to City
and Street
respectively.
var json = """
{
"name": "John",
"age": 30,
"address": {
"city": "New York",
"street": "Broadway"
}
}
""";
var reader = new JsonDataReader(json);
reader.Schema
.MapJsonProperty("name", "Name", typeof(string))
.MapJsonProperty("age", "Age", typeof(int))
.MapJsonProperty("address.city", "City", typeof(string))
.MapJsonProperty("address.street", "Street", typeof(string));
DataTable table = reader.ToDataTable();
/*
Output table is
| Name | Age | City | Street |
|--------------|-------------|-------------|--------------|
| ... | ... | ... | ... |
*/
Lazy / On-demand data type inference
If you are happy with how the JsonDataReader infers the schema, but simply needs to override how one (or more) data types are inferred, you can specify the OnResolveType callback and let JsonDataReader know which data type specific properties should have.
Example - Using OnResolveType for data type inference override
The example below shows how to override how JsonDataReader infers the data type of the weight
property by specifying the OnResolveType callback.
var json = """
{
"name": "John",
"age": 30,
"city": "New York",
"isStudent": false,
"dateOfBirth": "1990-01-01T00:00:00",
"weight": 70
}
""";
var reader = new JsonDataReader(json);
reader.Schema.OnResolveType = (propertyName) =>
{
return propertyName switch
{
"weight" => typeof(double), // We want weight to always be a decimal number, even if the value in the JSON document has no decimals.
_ => null // Let JsonDataReader infer the data type for the other properties.
};
};
var dataTable = reader.ToDataTable();
Properties
Name | Type | Description |
---|---|---|
Schema | JsonDataReaderSchema | Optional. Specifies the schema used by the JsonDataReader. |
FieldCount | Int32 | Returns the number of fields in the reader. Note that Read() must be called first if Schema is not defined. |
Methods
Name | Description |
---|---|
ToDataTable() | Returns the JSON documents as rows and columns in a DataTable. |
GetName(int columnIndex) | Gets the column name of the specified column index. |
GetOrdinal(string columnName) | Gets the column index of the specified column name. |
GetBoolean(int columnIndex) | Gets the value of the specified column as a Boolean. |
GetBoolean(string columnName) | Gets the value of the specified column as a Boolean. |
GetByte(int columnIndex) | Gets the value of the specified column as a byte. |
GetByte(string columnName) | Gets the value of the specified column as a byte. |
GetInt32(int columnIndex) | Gets the value of the specified column as a 32-bit signed integer. |
GetInt32(string columnName) | Gets the value of the specified column as a 32-bit signed integer. |
GetInt64(int columnIndex) | Gets the value of the specified column as a 64-bit signed integer. |
GetInt64(string columnName) | Gets the value of the specified column as a 64-bit signed integer. |
GetDecimal(int columnIndex) | Gets the value of the specified column as a Decimal. |
GetDecimal(string columnName) | Gets the value of the specified column as a Decimal. |
GetDouble(int columnIndex) | Gets the value of the specified column as a Double. |
GetDouble(string columnName) | Gets the value of the specified column as a Double. |
GetDateTime(int columnIndex) | Gets the value of the specified column as a DateTime. |
GetDateTime(int columnName) | Gets the value of the specified column as a DateTime. |
GetDateTimeOffset(int columnIndex) | Gets the value of the specified column as a DateTimeOffset. |
GetDateTimeOffset(int columnName) | Gets the value of the specified column as a DateTimeOffset. |
GetString(int columnIndex) | Gets the value of the specified column as a string. |
GetString(int columnName) | Gets the value of the specified column as a string. |
GetValue(int columnIndex) | Gets the value of the specified column in its native format. |
GetValue(string columnName) | Gets the value of the specified column its native format. |
Read() | Advances the reader to the next result. Returns false when there is no more data to process. |