Skip to content

Commit ff4e23b

Browse files
eaglethrosterunion
andauthored
feat(oas-to-har): retain null values in array (#998)
| 🚥 Resolves ISSUE_ID | | :------------------- | ## 🧰 Changes This PR is part of the work for the readme ticket [RM-9906 Not displaying null values in an array](https://linear.app/readme-io/issue/RM-9906/not-displaying-null-values-in-an-array) and should get merged ONLY after the corresponding PR changes in the `remove-undefined-objects` repo is shipped The aim of the ticket is we want user-added null values in arrays to be visible in requests. This requires adjustments in the oas-to-har package, with two separate causes of why the nulls in array are being dropped 1. For postData (body): - Issue: remove-undefined-objects was filtering out null values in arrays. - Fix: Replaced JSON.parse/stringify approach with a custom function to distinguish actual null vs. undefined. Updated stripEmptyObjects to stop removing null values in arrays, while still removing undefined, empty objects, and empty arrays. 2. For Non-postData (parameters, cookies, etc.): - Issue: encodeArray in style-serializer used .join, which dropped null values. - Fix: Convert null values to the string "null" before joining, so they’re preserved. TODO Before Merging: - [ ] Update the remove-undefined-object package version once this PR is shipped: [https://github.com/readmeio/remove-undefined-objects/pull/149](https://github.com/readmeio/remove-undefined-objects/pull/149) ## 🧬 QA & Testing - Temporarily migrated tests for remove-undefined-objects to show the changes works - Run oasToHar or oasToSnippet in a script with an OAS object containing arrays of null, undefined, and undefined objects --------- Co-authored-by: Jon Ursenbach <[email protected]>
1 parent 7e6b5ae commit ff4e23b

File tree

5 files changed

+92
-6
lines changed

5 files changed

+92
-6
lines changed

package-lock.json

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/oas-to-har/src/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ function isPrimitive(val: unknown) {
150150
}
151151

152152
function stringify(json: Record<string | 'RAW_BODY', unknown>) {
153-
return JSON.stringify(removeUndefinedObjects(typeof json.RAW_BODY !== 'undefined' ? json.RAW_BODY : json));
153+
return JSON.stringify(removeUndefinedObjects(typeof json.RAW_BODY !== 'undefined' ? json.RAW_BODY : json, { preserveNullishArrays: true }));
154154
}
155155

156156
function stringifyParameter(param: any): string {
@@ -420,7 +420,8 @@ export default function oasToHar(
420420

421421
if (operation.isFormUrlEncoded()) {
422422
if (Object.keys(formData.formData || {}).length) {
423-
const cleanFormData = removeUndefinedObjects(JSON.parse(JSON.stringify(formData.formData)));
423+
const cleanFormData = removeUndefinedObjects(formData.formData, { preserveNullishArrays: true });
424+
424425
if (cleanFormData !== undefined) {
425426
const postData: PostData = { params: [], mimeType: 'application/x-www-form-urlencoded' };
426427

@@ -444,7 +445,7 @@ export default function oasToHar(
444445

445446
if (isMultipart || isJSON) {
446447
try {
447-
let cleanBody = removeUndefinedObjects(JSON.parse(JSON.stringify(formData.body)));
448+
let cleanBody = removeUndefinedObjects(formData.body, { preserveNullishArrays: true });
448449

449450
if (isMultipart) {
450451
har.postData = { params: [], mimeType: 'multipart/form-data' };

packages/oas-to-har/src/lib/style-formatting/style-serializer.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,21 @@ function encodeArray({
119119
escape,
120120
isAllowedReserved = false,
121121
}: Omit<StylizerConfig, 'value'> & { value: string[] }) {
122-
const valueEncoder = (str: string) =>
123-
encodeDisallowedCharacters(str, {
122+
const valueEncoder = (str: string) => {
123+
// Handle null values explicitly to prevent join() from converting to empty string
124+
if (str === null) {
125+
return 'null';
126+
}
127+
128+
const result = encodeDisallowedCharacters(str, {
124129
escape,
125130
returnIfEncoded: location === 'query',
126131
isAllowedReserved,
127132
});
128133

134+
return result;
135+
};
136+
129137
switch (style) {
130138
/**
131139
* @example <caption>`style: simple`</caption>

packages/oas-to-har/test/parameters.test.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ describe('parameter handling', () => {
172172
parameters: [{ name: 'id', in: 'query' }],
173173
},
174174
{ query: { id: [null, null] } },
175-
[{ name: 'id', value: '&id=' }],
175+
[{ name: 'id', value: 'null&id=null' }],
176176
),
177177
);
178178

@@ -207,6 +207,28 @@ describe('parameter handling', () => {
207207
],
208208
},
209209
{ query: {} },
210+
[{ name: 'id', value: 'null&id=null' }],
211+
),
212+
);
213+
214+
it(
215+
'should handle mixed array with null, undefined, and normal values',
216+
assertQueryParams(
217+
{
218+
parameters: [{ name: 'id', in: 'query' }],
219+
},
220+
{ query: { id: [null, undefined, 'normal', null, 'test'] } },
221+
[{ name: 'id', value: 'null&id=&id=normal&id=null&id=test' }],
222+
),
223+
);
224+
225+
it(
226+
'should handle array with only undefined values',
227+
assertQueryParams(
228+
{
229+
parameters: [{ name: 'id', in: 'query' }],
230+
},
231+
{ query: { id: [undefined, undefined] } },
210232
[{ name: 'id', value: '&id=' }],
211233
),
212234
);

packages/oas-to-har/test/requestBody.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,52 @@ describe('request body handling', () => {
10851085
expect(har.log.entries[0].request.postData).toBeUndefined();
10861086
});
10871087

1088+
it('should preserve null values in arrays & still remove undefined values', () => {
1089+
const spec = Oas.init({
1090+
paths: {
1091+
'/requestBody': {
1092+
post: {
1093+
requestBody: {
1094+
content: {
1095+
'application/x-www-form-urlencoded': {
1096+
schema: {
1097+
type: 'object',
1098+
properties: {
1099+
foo: {
1100+
type: 'array',
1101+
items: {
1102+
type: 'string',
1103+
nullable: true,
1104+
},
1105+
},
1106+
foo2: {
1107+
type: 'array',
1108+
items: {
1109+
type: 'number',
1110+
},
1111+
},
1112+
},
1113+
},
1114+
},
1115+
},
1116+
},
1117+
},
1118+
},
1119+
},
1120+
});
1121+
1122+
const har = oasToHar(spec, spec.operation('/requestBody', 'post'), {
1123+
formData: { foo: [null, null, undefined], foo2: [1, 2] },
1124+
});
1125+
1126+
expect(har.log.entries[0].request.postData?.params).toStrictEqual([
1127+
// Since null is not a primitive, it will be JSON stringified which retains the square brackets
1128+
// See line 156-164 of src/index.ts
1129+
{ name: 'foo', value: '[null,null]' },
1130+
{ name: 'foo2', value: '1,2' },
1131+
]);
1132+
});
1133+
10881134
it('should pass in value if one is set and prioritize provided values', () => {
10891135
const spec = Oas.init({
10901136
paths: {

0 commit comments

Comments
 (0)