-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathparseJson.ts
More file actions
182 lines (167 loc) · 5.7 KB
/
parseJson.ts
File metadata and controls
182 lines (167 loc) · 5.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import { AdditionalOptions, SourceObjectOptions } from '../../types/index.js';
import { flattenJson, flattenJsonWithStringFilter } from './flattenJson.js';
import { JSONPath } from 'jsonpath-plus';
import { exitSync } from '../../console/logging.js';
import { logger } from '../../console/logger.js';
import {
findMatchingItemArray,
findMatchingItemObject,
generateSourceObjectPointers,
validateJsonSchema,
} from './utils.js';
import { applyStructuralTransforms } from './transformJson.js';
// Parse a JSON file according to a JSON schema
export function parseJson(
content: string,
filePath: string,
options: AdditionalOptions,
defaultLocale: string,
filterStrings: boolean = true
): string {
const jsonSchema = validateJsonSchema(options, filePath);
if (!jsonSchema) {
return content;
}
let json: any;
try {
json = JSON.parse(content);
} catch {
logger.error(`Invalid JSON file: ${filePath}`);
return exitSync(1);
}
if (jsonSchema.structuralTransform && jsonSchema.composite) {
applyStructuralTransforms(
json,
jsonSchema.structuralTransform,
jsonSchema.composite
);
}
// Handle include
if (jsonSchema.include) {
const flatten = filterStrings ? flattenJsonWithStringFilter : flattenJson;
const flattenedJson = flatten(json, jsonSchema.include);
return JSON.stringify(flattenedJson);
}
if (!jsonSchema.composite) {
logger.error('No composite property found in JSON schema');
return exitSync(1);
}
// Construct lvl 1
// Create mapping of sourceObjectPointer to SourceObjectOptions
const sourceObjectPointers: Record<
string,
{ sourceObjectValue: any; sourceObjectOptions: SourceObjectOptions }
> = generateSourceObjectPointers(jsonSchema.composite, json);
// Construct lvl 2
const sourceObjectsToTranslate: Record<
string,
Record<string, Record<string, string>> | string
> = {};
for (const [
sourceObjectPointer,
{ sourceObjectValue, sourceObjectOptions },
] of Object.entries(sourceObjectPointers)) {
// Skip if no includes
if (!sourceObjectOptions.include.length) {
continue;
}
// Find the default locale in each source item in each sourceObjectValue
// Array: use key field
if (sourceObjectOptions.type === 'array') {
// Validate type
if (!Array.isArray(sourceObjectValue)) {
logger.error(
`Source object value is not an array at path: ${sourceObjectPointer}`
);
return exitSync(1);
}
// Find matching source items
const matchingItems = findMatchingItemArray(
defaultLocale,
sourceObjectOptions,
sourceObjectPointer,
sourceObjectValue
);
if (!Object.keys(matchingItems).length) {
logger.error(
`Matching sourceItem not found at path: ${sourceObjectPointer} for locale: ${defaultLocale}. Check your JSON schema`
);
return exitSync(1);
}
// Construct lvl 3
const sourceItemsToTranslate: Record<string, Record<string, string>> = {};
for (const [arrayPointer, matchingItem] of Object.entries(
matchingItems
)) {
const { sourceItem, keyPointer } = matchingItem;
// Get the fields to translate from the includes
const matchingItemsToTranslate: any[] = [];
for (const include of sourceObjectOptions.include) {
try {
const matchingItems = JSONPath({
json: sourceItem,
path: include,
resultType: 'all',
flatten: true,
wrap: true,
});
if (matchingItems) {
matchingItemsToTranslate.push(...matchingItems);
}
} catch {
/* empty */
}
}
// Filter out the key pointer
sourceItemsToTranslate[arrayPointer] = Object.fromEntries(
matchingItemsToTranslate
.filter(
(item: { pointer: string; value: any }) =>
item.pointer !== keyPointer
)
.map((item: { pointer: string; value: string }) => [
item.pointer,
item.value,
])
);
}
// Add the items to translate to the result
sourceObjectsToTranslate[sourceObjectPointer] = sourceItemsToTranslate;
} else {
// Object: use the key in this object with the matching locale property
// Validate type
if (typeof sourceObjectValue !== 'object' || sourceObjectValue === null) {
logger.error(
`Source object value is not an object at path: ${sourceObjectPointer}`
);
return exitSync(1);
}
// Validate localeProperty
const matchingItem = findMatchingItemObject(
defaultLocale,
sourceObjectPointer,
sourceObjectOptions,
sourceObjectValue
);
// Validate source item exists
if (!matchingItem.sourceItem) {
logger.error(
`Source item not found at path: ${sourceObjectPointer}. You must specify a source item where its key matches the default locale`
);
return exitSync(1);
}
const { sourceItem } = matchingItem;
// If the source item is a string, use it directly
if (typeof sourceItem === 'string') {
sourceObjectsToTranslate[sourceObjectPointer] = sourceItem;
continue;
}
// Get the fields to translate from the includes
const flatten = filterStrings ? flattenJsonWithStringFilter : flattenJson;
const itemsToTranslate = flatten(sourceItem, sourceObjectOptions.include);
// Add the items to translate to the result
sourceObjectsToTranslate[sourceObjectPointer] = itemsToTranslate;
}
}
return JSON.stringify(sourceObjectsToTranslate);
}