Convert a string into an array of objects - javascript

Currently I have this string:
"Hello, I'm <%first%> <%last%>, and I <3 being a <%occupation%>. I am > blah";
How do I separate it into a an array of object that looks like this:
[
{ type: 'string', value: 'Hello, I'm ' },
{ type: 'token', value: 'first' },
{ type: 'string', value: ' ' },
{ type: 'token', value: 'last' },
{ type: 'string', value: ', and I <3 being a ' },
{ type: 'token', value: 'occupation' },
{type: 'string', value: 'I am > blah'}
]
The pattern of the string is that if we find a word that looks like this: <%word%>, then we put that into the array as an object with type token. Otherwise we put it in as a type string. My question is how do we implement it in code.
I'm having difficulty with forming the phrase which is going to be the value for the key value. Below is a code that I tried to implement, but it is faulty.
My idea is to have a word variable, which is empty, and as it goes through the string it concats the words to form the phases. Once it see that it's in between <% and %>, it will be given a type token and push into the arrStr array.
However 2 issues: 1) It seems that with this code, each iteration of the string from "H" to "He" to "Hel" to "Hell" to "Hello" is being generated. 2) It seems like it never touches token. 3) The first "h" in "hello" is somehow omitted.
How do I do this without using regex?

Using Replace Twice
Let's replace the angular brackets using some dummy string to split the data easily.
So, we replace <% by *# and %> only by *.
Then we split the data by only *. And after splitting we have the following array
[
"Hello, I'm ",
"#first",
" ",
"#last",
", and I <3 being a ",
"#occupation",
". I am > blah",
]
So, the extra # that we added to only the opening angular bracket will help us differentiate a token from a string.
Now we can simply map this array and get the desired result.
NOTE: You can use an appropriate dummy string that you're sure won't appear in the string.
const
str = "Hello, I'm <%first%> <%last%>, and I <3 being a <%occupation%>. I am > blah",
res = str
.replace(/<%/g, "*#")
.replace(/%>/g, "*")
.split("*")
.map((v) =>
v[0] === "#"
? { type: "token", value: v.slice(1) }
: { type: "string", value: v }
);
console.log(res);

var str = "Hello, I'm <%first%> <%last%>, and I <3 being a <%occupation%>. I am > blah";
var arr = str.split('<%');
var final = [], vala, thetype;
arr.forEach(val => {
vala=val.split('%>');
thetype='string';
if (vala.length>0) thetype='token';
final.push({type: thetype, value: vala[0]});
})

I'm sure regex will be a better solution, but I don't know it. So here is my approach:
let txt = "Hello, I'm <%first%> <%last%>, and I <3 being a <%occupation%>. I am > blah";
var arr = [];
txt.split("<%").forEach(x => {
if (x.includes("%>")) {
x.split("%>").forEach((y, i) => {
arr.push({type: i == 0 ? 'token' : 'string', value: y});
})
} else {
arr.push({type: 'string', value: x});
}
});
console.log(arr);

Using regex:
const convertToObjectArr = (str = '') => {
const reg = /<%(.*?)%>/g;
let arr = [];
let index = 0, lastIndex = 0;
while (match = reg.exec(str)) {
index = match.index;
if(index > lastIndex) {
arr.push({ type: 'string', value: str.substring(lastIndex, index) });
}
lastIndex = reg.lastIndex;
arr.push({ type: 'token', value: str.substring(lastIndex-2, index+2) });
}
if (lastIndex == 0) {
arr.push({ type: 'string', value: str.substring(lastIndex) });
} else if (lastIndex < str.length) {
arr.push({ type: 'token', value: str.substring(lastIndex) });
}
return arr;
}
console.log( convertToObjectArr("Hello, I'm <%first%> <%last%>, and I <3 being a <%occupation%>. I am > blah") );
console.log( convertToObjectArr("Hello") );
console.log( convertToObjectArr("Hello, I'm <%first%>") );

You could parse the string with a regexp like this:
const input = "Hello, I'm <%first%> <%last%>, and I <3 being a <%occupation%>. I am > blah";
const regex = /<%([^%]+)%>/g;
let result;
let parsed = [];
let lastIndex = 0;
while(result = regex.exec(input)) {
const { 1: match, index } = result;
parsed.push({ type: 'string', value: input.substring(lastIndex, index) });
parsed.push({ type: 'token', value: match });
lastIndex = index + match.length + 4;
}
parsed.push({ type: 'string', value: input.substring(lastIndex) });
console.log(parsed);

Here was my approach. You could simplify this a lot if you used regex (this is basically the exact use case for regex)
target = "Hello, I'm <%first%> <%last%>, and I <3 being a <%occupation%>. I am > blah"
const split = (el) => {
const parts = el.split('%>')
return [{type: 'token', value: parts[0]}, {type: 'string', value: parts[1]}]
}
const parts = target.split('<%')
.map(el =>
el.indexOf('%>') === -1
? {type: "string", value: el}
: split(el)
).flat()
console.log(parts)

You can use a lexer to tokenize your string. I have used moo in this example:
const lexer =
moo.compile({ token: {match: /<%.+?%>/, value: raw => raw.slice(2, -2)}
, strlt: /.+?(?=<%)/
, strrt: /(?<=%>).+/ });
lexer.reset("Hello, I'm <%first%> <%last%>, and I <3 being a <%occupation%>. I am > blah");
console.log(
[...lexer].map(({ type, value }) =>
({ type: (type === 'token' ? type : 'string')
, value }))
);
<script src="https://unpkg.com/moo#0.5.1/moo.js"></script>

Related

how to modify string with special characters to array of objects in javascript

I have a string with special characters, I have to modify to array of object using javascript
so in a string, always before underscore considered as name
and after underscore is considered as type
if semicolon is present, then can be two diff objects
tried
var arr = str2.split(';');
var result = arr.find( e => {
e.split(/[_,]/);
}).map(i => ({
name: i[0],
type: i.shift()
}));
var str1 = "xyz_dell,asus";
var str2 = "abc_new;red_old;zen_sg,my"
Expected Output for str1
[
{name: xyz, type: dell,asus}
]
Expected Output for str2
[
{name: abc, type: new},
{name: red, type: old},
{name: zen, type: sg, my}
]
Use split first on ; and use flatMap and use split on _
const process = (str) =>
str.split(";").flatMap((item) => {
const [name, type] = item.split("_");
return { name, type };
});
var str1 = "xyz_dell,asus";
var str2 = "abc_new;red_old;zen_sg,my";
console.log(process(str1));
console.log(process(str2));

Dynamically splitting strings with multiple keywords

I'm working on headless CMS using Angular.
In the content below, whatever is wrapped inside {{ }} is considered to be an anchor link.
We processes your data in accordance with the {{policy_placeholder}}. You have the right to object to the processing of your personal data. Check “Your rights” in the {{policy_placeholder}} and {{term_policy}} for more information.;`
So with the above example, below is the expected result
[
{
type: "TEXT",
content: "We processes your data in accordance with the "
},
{
type: "LINK",
content: "{{policy_placeholder}}"
},
{
type: "TEXT",
content:
". You have the right to object to the processing of your personal data. Check “Your rights” in the "
},
{
type: "LINK",
content: "{{policy_placeholder}}"
},
{
type: "TEXT",
content: " and "
},
{
type: "LINK",
content: "{{terms_placeholder}}"
},
{
type: "TEXT",
content: " for more information."
}
];
Below is what I tried
splitString = function(string, splitters) {
var list = [string];
for(var i=0, len=splitters.length; i<len; i++) {
traverseList(list, splitters[i], 0);
}
const x = flatten(list);
console.log(x);
return flatten(list);
}
traverseList = function(list, splitter, index) {
if(list[index]) {
if((list.constructor !== String) && (list[index].constructor === String))
(list[index] != list[index].split(splitter)) ? list[index] = list[index].split(splitter) : null;
(list[index].constructor === Array) ? traverseList(list[index], splitter, 0) : null;
(list.constructor === Array) ? traverseList(list, splitter, index+1) : null;
}
}
flatten = function(arr) {
return arr.reduce(function(acc, val) {
return acc.concat(val.constructor === Array ? flatten(val) : val);
},[]);
}
var splitList = ["{{policy_placeholder}}", "{{term_policy}}"];
splitString(source, splitList);
The issue is that I've to manually add the splitList, but i want to make it dynamic based on {{ }}
How can this be done?
When you spread a string, you actually split it into characters.
const source = `We processes your data in accordance with the {{policy_placeholder1}}. You have the right to object to the processing of your personal data. Check “Your rights” in the {{policy_placeholder2}} for more information.`;
function splitString(str) {
const ans = [];
const linkTokenRegex = /\{\{.+?\}\}/g;
const textsArr = str.split(linkTokenRegex);
const linksArr = str.match(linkTokenRegex);
textsArr.forEach((textPart, index) => {
ans.push({
type: "TEXT",
content: textPart,
});
if (linksArr[index]) {
ans.push({
type: "LINK",
content: linksArr[index],
});
}
});
return ans;
}
console.log(splitString(source));

How to find a value in an array of objects?

I have an array of objects like this:
var data = [
{id:1,first_name:'John',last_name:'Doe',age:20,birth:'1992-01-12'},
{id:2,first_name:'Blaan',last_name:'Adey',age:35,birth:'2001-04-16'}
];
var searchColumn = ['first_name','last_name','birth'];
These values(searchColumn) ​​may change and not be constant.
For this reason they must be read from the array
I want to do this now :
function searchData(Val){
var newData = [];
if (Val){
for (let i=0 ; i < data.length ; i++){
searchColumn.forEach(value => {
if (data[i][value].includes(Val)){
newData.push(data[i]);
}
}
}
}
}
searchData('L');
please guide me.
This should do.
You don't have to loop through the array, you can use build-in functions like map, some and filter to achieve this.
var data = [
{id:1,first_name:'John',last_name:'Doe',age:20,birth:'1992-01-12'},
{id:1,first_name:'John',last_name:'Bal',age:20,birth:'1992-01-12'},
{id:2,first_name:'Blaan',last_name:'Adey',age:35,birth:'2001-04-16'}
];
var searchColumn = ['first_name','last_name','birth'];
var search = (searchParam) => {
// map here applies the function (value) => searchColumn.some(property => value[property] === searchParam) ? value : null
// over every value in the array & some loops through every value in the search column
// looking for an object where one of the properties specified in searchColumn has a value that matches our search parameter
return data.map(value => searchColumn.some(property => value[property] === searchParam) ? value : null)
.filter(result => !!result); // here we use filter to remove the null values mapped in the map function
}
search('John');
// result -> [{id:1,first_name:'John',last_name:'Doe',age:20,birth:'1992-01-12'},
// -> {id:1,first_name:'John',last_name:'Bal',age:20,birth:'1992-01-12'}]
UPDATE: As suggested, an better solution:
var search = (searchParam) => {
return data.filter(value => !!searchColumn.some(property => value[property] === searchParam));
}
... a configurable, thus more generic, approach based on Array.prototype.filter and Function.prototype.bind ...
function doesItemMatchBoundPropertyValueAndSearch(item) {
const { keyList, search } = this;
return keyList.some(key => item[key].includes(search));
}
const sampleDataList = [
{id: 1, first_name: 'John', last_name: 'Doe', age: 20, birth: '1992-01-12'},
{id: 2, first_name: 'Blaan', last_name: 'Adey', age: 35, birth: '2001-04-16'},
];
const searchColumn = ['first_name', 'last_name', 'birth'];
console.log(
"search: 'L' :",
sampleDataList.filter(
doesItemMatchBoundPropertyValueAndSearch.bind({
keyList: searchColumn,
search: 'L',
})
)
);
console.log(
"search: 'o' :",
sampleDataList.filter(
doesItemMatchBoundPropertyValueAndSearch.bind({
keyList: searchColumn,
search: 'o',
})
)
);
console.log(
"search: 'n' :",
sampleDataList.filter(
doesItemMatchBoundPropertyValueAndSearch.bind({
keyList: searchColumn,
search: 'n',
})
)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

Refactor array of objects

The below code will have input as array of objects and I would like to convert into a different format.
The below code works fine but I need a more refactored shorter format of what I am trying to achieve.
var res = {"matchObject":"{\"data\":[{\"id\":\"jack1\",\"firstname\":\"jack\",\"lastname\":\"hudson\",\"dob\":\"1990-01-01T00:00:00.000Z\",\"email\":\"jack1#yahoo.com\",\"phone\":null,\"orgid\":\"001\"},{\"id\":\"jack2\",\"firstname\":\"Jack\",\"lastname\":\"Clinton\",\"dob\":\"1991-01-01T00:00:00.000Z\",\"email\":\"jack.clinton#yahoo.com\",\"phone\":\"+16464922600\",\"orgid\":\"002\"}]}"};
var parsedObj = JSON.parse(res.matchObject);
var res = [];
for(var key in parsedObj.data){
var emailObj = {};
var phoneObj = {}
if(parsedObj.data[key].email !== null){
emailObj.matchedRes = parsedObj.data[key].email;
emailObj.id = parsedObj.data[key].id;
emailObj.type = "email";
res.push(emailObj);
}
if(parsedObj.data[key].phone !== null){
phoneObj.matchedRes = parsedObj.data[key].phone;
phoneObj.id = parsedObj.data[key].id;
phoneObj.type="phone";
res.push(phoneObj);
}
}
console.log(res);
Desired output:
[ { matchedRes: 'jack1#yahoo.com', id: 'jack1', type: 'email' },
{ matchedRes: 'jack.clinton#yahoo.com', id: 'jack2', type: 'email' },
{ matchedRes: '+16464922600', id: 'jack2', type: 'phone' } ]
In the above code separate objects are created with phone and email for same id.
Here is a solution!
I just did a generic reducer, and then I use it on phone and email.
Then, I just spread the result of both calls to the result array :)
var res = {"matchObject":"{\"data\":[{\"id\":\"jack1\",\"firstname\":\"jack\",\"lastname\":\"hudson\",\"dob\":\"1990-01-01T00:00:00.000Z\",\"email\":\"jack1#yahoo.com\",\"phone\":null,\"orgid\":\"001\"},{\"id\":\"jack2\",\"firstname\":\"Jack\",\"lastname\":\"Clinton\",\"dob\":\"1991-01-01T00:00:00.000Z\",\"email\":\"jack.clinton#yahoo.com\",\"phone\":\"+16464922600\",\"orgid\":\"002\"}]}"};
var parsedObj = JSON.parse(res.matchObject);
const extractData = (obj, type) => obj.reduce((acc, elt) => (
elt[type] && acc.push({matchedRes: elt[type], id: elt.id, type: type})
, acc),[]);
const result = [...extractData(parsedObj.data, 'email'), ...extractData(parsedObj.data, 'phone')];
console.log(result);
Hope this helps, please do not hesitate to comment if you have any question ;)
You can use reduce with destructuring assignment . and check if email or phone is there than add a object accordingly
var res = {"matchObject":"{\"data\":[{\"id\":\"jack1\",\"firstname\":\"jack\",\"lastname\":\"hudson\",\"dob\":\"1990-01-01T00:00:00.000Z\",\"email\":\"jack1#yahoo.com\",\"phone\":null,\"orgid\":\"001\"},{\"id\":\"jack2\",\"firstname\":\"Jack\",\"lastname\":\"Clinton\",\"dob\":\"1991-01-01T00:00:00.000Z\",\"email\":\"jack.clinton#yahoo.com\",\"phone\":\"+16464922600\",\"orgid\":\"002\"}]}"};
var parsedObj = JSON.parse(res.matchObject);
let op = parsedObj.data.reduce((out,{id,email,phone})=>{
if(email){
out.push({matchedRes:email,id,type:`email`})
}
if(phone){
out.push({matchesRes:phone,id,type:`phone`})
}
return out
},[])
console.log(op)
If you want to see more use cases of You can destructuring assignment and it's uses you can check this one out
This should be possible with reduce:
var res = {"matchObject":"{\"data\":[{\"id\":\"jack1\",\"firstname\":\"jack\",\"lastname\":\"hudson\",\"dob\":\"1990-01-01T00:00:00.000Z\",\"email\":\"jack1#yahoo.com\",\"phone\":null,\"orgid\":\"001\"},{\"id\":\"jack2\",\"firstname\":\"Jack\",\"lastname\":\"Clinton\",\"dob\":\"1991-01-01T00:00:00.000Z\",\"email\":\"jack.clinton#yahoo.com\",\"phone\":\"+16464922600\",\"orgid\":\"002\"}]}"};
var parsedObj = JSON.parse(res.matchObject);
const keyFields = ["email", "phone"];
let result = parsedObj.data.reduce((acc, val) => {
keyFields.forEach(k => {
if (val[k]) acc.push({ matchedRes: val.email, id: val.id, type: k});
});
return acc;
}, []);
console.log("Result: ", result);
If you are looking for a little shorter code but still easy to read for anybody:
var res = {"matchObject":"{\"data\":[{\"id\":\"jack1\",\"firstname\":\"jack\",\"lastname\":\"hudson\",\"dob\":\"1990-01-01T00:00:00.000Z\",\"email\":\"jack1#yahoo.com\",\"phone\":null,\"orgid\":\"001\"},{\"id\":\"jack2\",\"firstname\":\"Jack\",\"lastname\":\"Clinton\",\"dob\":\"1991-01-01T00:00:00.000Z\",\"email\":\"jack.clinton#yahoo.com\",\"phone\":\"+16464922600\",\"orgid\":\"002\"}]}"};
var parsedObj = JSON.parse(res.matchObject);
var res = [];
Object.entries(parsedObj.data).forEach(el => {
el = el[1]
if(el.email !== null)
res.push({
matchedRes: el.email,
id: el.id,
type: "email"
})
if(el.phone !== null)
res.push({
matchedRes: el.phone,
id: el.id,
type: "phone"
})
})
console.log(res);

FormData append nested object

Is it possible to append nested object to FormData?
let formData = new FormData();
let data = {
title: 'title',
text: 'text',
preview: {p_title:'p title', p_text: 'p text'}
};
$.each(data, function(key, value) {
formData.append(key, value);
});
Server console - console.log(req.body)
{
title: 'title',
text: 'text',
preview: '[object Object]'
}
How can I get the exact value of preview: {p_title:'p title', p_text: 'p text'}?
let formData = new FormData();
let data = {
title: 'title',
text: 'text',
preview: {p_title:'p title', p_text: 'p text'}
};
for(let dataKey in data) {
if(dataKey === 'preview') {
// append nested object
for (let previewKey in data[dataKey]) {
formData.append(`preview[${previewKey}]`, data[dataKey][previewKey]);
}
}
else {
formData.append(dataKey, data[dataKey]);
}
}
Console formData
for (let val of formData.entries()) {
console.log(val[0]+ ', ' + val[1]);
}
To append an object to formData, you need to stringify it first, like this:
let objToAppend= {
key1: value1,
key2: value2,
}
let formData = new FormData();
formData.append('obj', JSON.stringify(objToAppend));
Then on the server side to access it you need to parse it first using JSON.parse().
Hope it helps!
First of all I apologize for my bad English.
I did something to get the data properly on the server side, not when I was writing to the console. I hope you want to do this.
I had to write a javascript function to get the client side nested data on the server side.
For this I wrote the obj2FormData() function. Square brackets seem to work.
function obj2FormData(obj, formData = new FormData()){
this.formData = formData;
this.createFormData = function(obj, subKeyStr = ''){
for(let i in obj){
let value = obj[i];
let subKeyStrTrans = subKeyStr ? subKeyStr + '[' + i + ']' : i;
if(typeof(value) === 'string' || typeof(value) === 'number'){
this.formData.append(subKeyStrTrans, value);
} else if(typeof(value) === 'object'){
this.createFormData(value, subKeyStrTrans);
}
}
}
this.createFormData(obj);
return this.formData;
}
When sending data with Ajax, we convert the nested object to the FormData object.
let formData = obj2FormData({
name : 'Emrah',
surname : 'Tuncel',
birth: 1983,
child : {
name: 'Eylul',
surname: 'Tuncel',
toys: ['ball', 'baby']
},
color: ['red', 'yellow']
});
Now let's send the FormData that we transformed with ajax.
const xhr = new XMLHttpRequest();
xhr.addEventListener('load', response => {
//AJAX RESPONSE >>
console.log(response);
//AJAX RESPONSE //
});
xhr.open('POST','response.php');
xhr.send(formData);
When I pressed the data to the screen with PHP, I got the exact result I wanted. It doesn't matter whether the method is POST or GET.
response.php
<pre><? print_r($_GET) ?></pre>
<pre><? print_r($_POST) ?></pre>
The output was as follows.
Array
(
[name] => Emrah
[surname] => Tuncel
[birth] => 1983
[child] => Array
(
[name] => Eylul
[surname] => Tuncel
[toys] => Array
(
[0] => ball
[1] => baby
)
)
[color] => Array
(
[0] => red
[1] => yellow
)
)
Hopefully it benefits your business.
FormData values are automatically converted to string. You can try to do it using Blob.
Or just put it as string using JSON.stringify(obj).
$.each(data, function(key, value){
if (typeof(value) === 'object') {
value = new Blob([JSON.stringify(value)], {type : 'application/json'});// or just JSON.stringify(value)
}
formData.append(key, value);
});
Here is a "A convenient JavaScript function that converts an object to a FormData instance" github, also available as a npm package, very simple to use
let data = {
title: 'title',
text: 'text',
preview: {p_title:'p title', p_text: 'p text'}
};
var formData = objectToFormData(data);
This for me do the work:
use . to separate key and subKey
let formData = new FormData();
let data = {
title: 'title',
text: 'text',
preview: {p_title:'p title', p_text: 'p text'}
};
for(let key in data) {
if(typeof(data[key]) === 'object') {
for (let subKey in data[key]) {
formData.append(`${key}.${subKey}`, data[key][subKey]);
}
}
else {
formData.append(key, data[key]);
}
}
Try out object-to-formdata. It's a convenient JavaScript function that converts Objects to FormData instances.
import { objectToFormData } from 'object-to-formdata';
const object = {
/**
* key-value mapping
* values can be primitives or objects
*/
};
const options = {
/**
* include array indices in FormData keys
* defaults to false
*/
indices: false,
/**
* treat null values like undefined values and ignore them
* defaults to false
*/
nullsAsUndefineds: false,
/**
* convert true or false to 1 or 0 respectively
* defaults to false
*/
booleansAsIntegers: false,
};
const formData = objectToFormData(
object,
options, // optional
existingFormData, // optional
keyPrefix, // optional
);
console.log(formData);
You don't need to use any third party modules and JSON.stringify() isn't ideal if you have nested objects. Instead you can use Object.entries().
// If this is the object you want to convert to FormData...
const item = {
description: 'First item',
price: 13,
photo: File
};
const formData = new FormData();
Object.entries(item).forEach(([key, value]) => {
formData.append(key, value);
});
// At this point, you can then pass formData to your handler method
Read more about Object.entries() over here - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
use hashName[keyName]
let formData = new FormData();
let data = {
title: 'title',
text: 'text',
preview: {p_title:'p title', p_text: 'p text'}
};
for(let key in data) {
if(typeof(data[key]) === 'object') {
for (let subKey in data[key]) {
formData.append('${key}[${subKey}]', data[key][subKey]);
}
}
else {
formData.append(key, data[key]);
}
}
I hope I can help, I did it in a simpler way and I believe it works for all hypotheses, please test:
const parses = []
const fKey = key => ((function until(value, comp = value) {
const result = value.replace(/\.([A-z0-9]*)(\.|$)/, '[$1]$2')
return comp !== result ? until(result, value) : result
})(key))
function populateFormData(values, form = new FormData(), base = '') {
Object.keys(values).forEach(key => {
const value = values[key]
if (typeof value == 'string' ||
typeof value == 'number' ||
typeof value == 'boolean')
{
form.append(fKey(`${base}${key}`), value)
parses.push({
key: `${fKey(`${base}${key}`)}`,
value
})
}
else if (typeof value == 'object')
{
populateFormData(value, form, `${base}${key}.`)
}
})
return form;
}
populateFormData({
title: 'Lucas',
text: 'Is good :)',
preview: {
p_title: 'I am a P title',
p_text: 'I am a P text',
test: {
example: 2,
my: {
obj: [
'eba',
{
hyper: 'text'
},
123
],
yes: true
}
}
}
})
console.log(parses)
I guess #Emrah Tuncel's solution is really fine, maybe with some improvement:
function object_to_form_data(data,formData,index = '') {
for (const objKey in data) {
const currentValue = data[objKey];
const currentIndex = index ? `${index}[${objKey}]` : objKey;
// Verify that currentValue is not an array, as arrays are also objects in js
if (typeof currentValue === 'object' && !Array.isArray(currentValue)) {
// If the currently iterated object is an empty object, we must not append it to the
// formData, as that one will get appended as empty JSON object string too. In that case, as even null will
// get parsed to "null" with formData, add a 'none' string as the value, and handle the
// respective cases on the server side
if (Object.keys(currentValue).length === 0) {
formData.append(currentIndex,'none'); // You may change this to however you wanna handle empty objects
continue;
}
object_to_form_data(currentValue,formData,currentIndex);
} else {
formData.append(currentIndex,currentValue);
}
}
}
The main thing was that you must consider arrays and handle their special case, as they also enter the "typeof === 'object'" loop in javascript. You should also handle the special case where you have an empty object value within your nested object. Now, at least for my use-cases, it's fully compatible with my object-validations and sanitizations on the server-side.
I think it can be a good solution for you:
function getFormData (obj = {}, formData = new FormData(), key = '') {
if (!([Array, File, Object].includes(obj.constructor))) {
return formData;
}
// Handle File recursive
if (obj instanceof File) {
formData.append(key, obj);
return formData;
}
for (const prop in obj) {
// Validate value type
if (obj[prop] && !([String, Number, Boolean, Array, Object, File].includes(obj[prop].constructor))) {
continue;
}
// Set deep index of prop
const deepKey = key ? key + `[${prop}]` : prop;
// Handle array
if (Array.isArray(obj[prop])) {
obj[prop].forEach((item, index) => {
getFormData(item, formData, `${deepKey}[${index}]`);
})
continue;
}
(typeof obj[prop] === 'object' && obj[prop] && obj[prop].constructor === Object)
? getFormData(obj[prop], formData, deepKey) // Handle object
: formData.append(deepKey, [undefined, null].includes(obj[prop]) ? '' : obj[prop]) // Handle string, number, boolean
}
return formData;
}
const data = {
string: 'abcd...',
number: 1234,
boolean: true,
boolean2: false,
object: {
index: 1,
value: 'value',
boolean3: false,
},
array: [
{
index: 2,
value: 'value 2'
},
{
index: 3,
value: 'value 3'
},
[
{
index: 4,
value: 'value 2'
},
{
index: 5,
value: 'value 3',
boolean4: false,
null: null,
function: (x,y) => { return x + y; },
},
true,
undefined,
(x,y) => { return x + y; },
new File(["woo"], 'woo.txt')
],
false,
new File(["foo"], 'file.txt')
],
};
const formData = getFormData(data);
for (let pair of formData.entries()) {
console.log(pair[0]+ ', ' + pair[1]);
}

Categories

Resources