Skip to content

Commit cd11447

Browse files
authored
feat: support and optionally parse JSON field (#1229)
1 parent 6d8e77e commit cd11447

File tree

7 files changed

+195
-64
lines changed

7 files changed

+195
-64
lines changed

samples/insertingDataTypes.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ function main(datasetId = 'my_dataset', tableId = 'my_table') {
4545
name: 'school',
4646
type: 'BYTES',
4747
},
48+
{
49+
name: 'metadata',
50+
type: 'JSON',
51+
},
4852
{
4953
name: 'location',
5054
type: 'GEOGRAPHY',
@@ -108,14 +112,19 @@ function main(datasetId = 'my_dataset', tableId = 'my_table') {
108112
const bqTimestamp = bigquery.timestamp('2020-04-27T18:07:25.356Z');
109113
const bqGeography = bigquery.geography('POINT(1 2)');
110114
const schoolBuffer = Buffer.from('Test University');
111-
115+
// a JSON field needs to be converted to a string
116+
const metadata = JSON.stringify({
117+
owner: 'John Doe',
118+
contact: '[email protected]',
119+
});
112120
// Rows to be inserted into table
113121
const rows = [
114122
{
115123
name: 'Tom',
116124
age: '30',
117125
location: bqGeography,
118126
school: schoolBuffer,
127+
metadata: metadata,
119128
measurements: [50.05, 100.5],
120129
datesTimes: {
121130
day: bqDate,

src/bigquery.ts

+33-14
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ export type Query = JobRequest<bigquery.IJobConfigurationQuery> & {
104104
jobTimeoutMs?: number;
105105
pageToken?: string;
106106
wrapIntegers?: boolean | IntegerTypeCastOptions;
107+
parseJSON?: boolean;
107108
};
108109

109110
export type QueryParamTypeStruct = {
@@ -122,6 +123,7 @@ export type QueryParamTypes =
122123
export type QueryOptions = QueryResultsOptions;
123124
export type QueryStreamOptions = {
124125
wrapIntegers?: boolean | IntegerTypeCastOptions;
126+
parseJSON?: boolean;
125127
};
126128
export type DatasetResource = bigquery.IDataset;
127129
export type ValueType = bigquery.IQueryParameterType;
@@ -476,24 +478,29 @@ export class BigQuery extends Service {
476478
*
477479
* @param {object} schema
478480
* @param {array} rows
479-
* @param {boolean|IntegerTypeCastOptions} wrapIntegers Wrap values of
481+
* @param {object} options
482+
* @param {boolean|IntegerTypeCastOptions} options.wrapIntegers Wrap values of
480483
* 'INT64' type in {@link BigQueryInt} objects.
481484
* If a `boolean`, this will wrap values in {@link BigQueryInt} objects.
482485
* If an `object`, this will return a value returned by
483486
* `wrapIntegers.integerTypeCastFunction`.
484487
* Please see {@link IntegerTypeCastOptions} for options descriptions.
485-
* @param {array} selectedFields List of fields to return.
488+
* @param {array} options.selectedFields List of fields to return.
486489
* If unspecified, all fields are returned.
490+
* @param {array} options.parseJSON parse a 'JSON' field into a JSON object.
487491
* @returns Fields using their matching names from the table's schema.
488492
*/
489493
static mergeSchemaWithRows_(
490494
schema: TableSchema | TableField,
491495
rows: TableRow[],
492-
wrapIntegers: boolean | IntegerTypeCastOptions,
493-
selectedFields?: string[]
496+
options: {
497+
wrapIntegers: boolean | IntegerTypeCastOptions;
498+
selectedFields?: string[];
499+
parseJSON?: boolean;
500+
}
494501
) {
495-
if (selectedFields && selectedFields!.length > 0) {
496-
const selectedFieldsArray = selectedFields!.map(c => {
502+
if (options.selectedFields && options.selectedFields!.length > 0) {
503+
const selectedFieldsArray = options.selectedFields!.map(c => {
497504
return c.split('.');
498505
});
499506

@@ -505,7 +512,7 @@ export class BigQuery extends Service {
505512
.map(c => c!.toLowerCase())
506513
.indexOf(field.name!.toLowerCase()) >= 0
507514
);
508-
selectedFields = selectedFieldsArray
515+
options.selectedFields = selectedFieldsArray
509516
.filter(c => c.length > 0)
510517
.map(c => c.join('.'));
511518
}
@@ -518,10 +525,10 @@ export class BigQuery extends Service {
518525
let value = field.v;
519526
if (schemaField.mode === 'REPEATED') {
520527
value = (value as TableRowField[]).map(val => {
521-
return convert(schemaField, val.v, wrapIntegers, selectedFields);
528+
return convert(schemaField, val.v, options);
522529
});
523530
} else {
524-
value = convert(schemaField, value, wrapIntegers, selectedFields);
531+
value = convert(schemaField, value, options);
525532
}
526533
// eslint-disable-next-line @typescript-eslint/no-explicit-any
527534
const fieldObject: any = {};
@@ -534,8 +541,11 @@ export class BigQuery extends Service {
534541
schemaField: TableField,
535542
// eslint-disable-next-line @typescript-eslint/no-explicit-any
536543
value: any,
537-
wrapIntegers: boolean | IntegerTypeCastOptions,
538-
selectedFields?: string[]
544+
options: {
545+
wrapIntegers: boolean | IntegerTypeCastOptions;
546+
selectedFields?: string[];
547+
parseJSON?: boolean;
548+
}
539549
) {
540550
if (is.null(value)) {
541551
return value;
@@ -558,6 +568,7 @@ export class BigQuery extends Service {
558568
}
559569
case 'INTEGER':
560570
case 'INT64': {
571+
const {wrapIntegers} = options;
561572
value = wrapIntegers
562573
? typeof wrapIntegers === 'object'
563574
? BigQuery.int(
@@ -580,8 +591,7 @@ export class BigQuery extends Service {
580591
value = BigQuery.mergeSchemaWithRows_(
581592
schemaField,
582593
value,
583-
wrapIntegers,
584-
selectedFields
594+
options
585595
).pop();
586596
break;
587597
}
@@ -605,6 +615,11 @@ export class BigQuery extends Service {
605615
value = BigQuery.geography(value);
606616
break;
607617
}
618+
case 'JSON': {
619+
const {parseJSON} = options;
620+
value = parseJSON ? JSON.parse(value) : value;
621+
break;
622+
}
608623
default:
609624
break;
610625
}
@@ -1311,6 +1326,7 @@ export class BigQuery extends Service {
13111326
* the format of the {@link https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets#DatasetReference| `DatasetReference`}
13121327
* @param {boolean} [options.wrapIntegers] Optionally wrap INT64 in BigQueryInt
13131328
* or custom INT64 value type.
1329+
* @param {boolean} [options.parseJSON] Optionally parse JSON as a JSON Object.
13141330
* @param {object|array} [options.params] Option to provide query prarameters.
13151331
* @param {JobCallback} [callback] The callback function.
13161332
* @param {?error} callback.err An error returned while making this request.
@@ -2041,6 +2057,7 @@ export class BigQuery extends Service {
20412057
typeof query === 'object'
20422058
? {
20432059
wrapIntegers: query.wrapIntegers,
2060+
parseJSON: query.parseJSON,
20442061
}
20452062
: {};
20462063
const callback =
@@ -2073,20 +2090,22 @@ export class BigQuery extends Service {
20732090
return;
20742091
}
20752092

2076-
const {location, maxResults, pageToken, wrapIntegers} = query;
2093+
const {location, maxResults, pageToken, wrapIntegers, parseJSON} = query;
20772094

20782095
const opts = {
20792096
location,
20802097
maxResults,
20812098
pageToken,
20822099
wrapIntegers,
2100+
parseJSON,
20832101
autoPaginate: false,
20842102
};
20852103

20862104
delete query.location;
20872105
delete query.maxResults;
20882106
delete query.pageToken;
20892107
delete query.wrapIntegers;
2108+
delete query.parseJSON;
20902109

20912110
this.query(query, opts, callback);
20922111
}

src/job.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export type CancelResponse = [bigquery.IJobCancelResponse];
4949
export type QueryResultsOptions = {
5050
job?: Job;
5151
wrapIntegers?: boolean | IntegerTypeCastOptions;
52+
parseJSON?: boolean;
5253
} & PagedRequest<bigquery.jobs.IGetQueryResultsParams>;
5354

5455
/**
@@ -538,6 +539,8 @@ class Job extends Operation {
538539

539540
const wrapIntegers = qs.wrapIntegers ? qs.wrapIntegers : false;
540541
delete qs.wrapIntegers;
542+
const parseJSON = qs.parseJSON ? qs.parseJSON : false;
543+
delete qs.parseJSON;
541544

542545
delete qs.job;
543546

@@ -559,11 +562,10 @@ class Job extends Operation {
559562
let rows: any = [];
560563

561564
if (resp.schema && resp.rows) {
562-
rows = BigQuery.mergeSchemaWithRows_(
563-
resp.schema,
564-
resp.rows,
565-
wrapIntegers
566-
);
565+
rows = BigQuery.mergeSchemaWithRows_(resp.schema, resp.rows, {
566+
wrapIntegers,
567+
parseJSON,
568+
});
567569
}
568570

569571
let nextQuery: QueryResultsOptions | null = null;

src/table.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export type TableRowValue = string | TableRow;
114114

115115
export type GetRowsOptions = PagedRequest<bigquery.tabledata.IListParams> & {
116116
wrapIntegers?: boolean | IntegerTypeCastOptions;
117+
parseJSON?: boolean;
117118
};
118119

119120
export type JobLoadMetadata = JobRequest<bigquery.IJobConfigurationLoad> & {
@@ -1811,6 +1812,8 @@ class Table extends ServiceObject {
18111812
typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
18121813
const wrapIntegers = options.wrapIntegers ? options.wrapIntegers : false;
18131814
delete options.wrapIntegers;
1815+
const parseJSON = options.parseJSON ? options.parseJSON : false;
1816+
delete options.parseJSON;
18141817
const onComplete = (
18151818
err: Error | null,
18161819
rows: TableRow[] | null,
@@ -1821,12 +1824,13 @@ class Table extends ServiceObject {
18211824
callback!(err, null, null, resp);
18221825
return;
18231826
}
1824-
rows = BigQuery.mergeSchemaWithRows_(
1825-
this.metadata.schema,
1826-
rows || [],
1827-
wrapIntegers,
1828-
options.selectedFields ? options.selectedFields!.split(',') : []
1829-
);
1827+
rows = BigQuery.mergeSchemaWithRows_(this.metadata.schema, rows || [], {
1828+
wrapIntegers: wrapIntegers,
1829+
selectedFields: options.selectedFields
1830+
? options.selectedFields!.split(',')
1831+
: [],
1832+
parseJSON,
1833+
});
18301834
callback!(null, rows, nextQuery, resp);
18311835
};
18321836

0 commit comments

Comments
 (0)