I'm using axios and my backend is laravel/php.
I should send an form-data like this:
national_id:123
social_media[0][social_id]:1
social_media[0][username]:myuser
my data:
var social_media = []
let newSocial = {
"social_id": 1,
"username": myuser
}
social_media.push(newSocial)
const data = {
"national_id": national_id,
"social_media": social_media,
"image":image
}
my axios post:
updateUser = (data) => {
const headers = {
'content-type': 'multipart/form-data'
}
let formData = new FormData();
for ( var key in data ) {
formData.append(key, data[key]);
}
return this.init(headers).post("/users/"+data.user_id, formData);
};
but I got this error:
errors: {social_media: ["The social media must be an array."]}
message: "The given data was invalid."
my network tab:
national_id: 123
social_media: [object Object]
It's hard to tell without seeing the code used by the server to decode the data.
However, the first thing I'd try, assuming no access to server code, is encoding the array as a JSON string:
change this:
const data = {
"national_id": national_id,
"social_media": social_media,
"image":image
}
to this:
const data = {
"national_id": national_id,
"social_media": JSON.stringify(social_media),
"image":image
}
Since you are sending an array, according to https://developer.mozilla.org/en-US/docs/Web/API/FormData/append, you can send multiple values with the same name:
formData.append('userpic[]', myFileInput.files[0], 'chris1.jpg');
formData.append('userpic[]', myFileInput.files[1], 'chris2.jpg');
Also, you might need to stringify the objects with JSON.stringify.
So for your case, you can do something like this
let formData = new FormData();
for (var key in data) {
if (Array.isArray(data[key])) {
for (var i = 0; i < data[key].length; i++) {
formData.append(`${key}[]`, JSON.stringify(data[key][i]));
}
} else {
formData.append(key, data[key]);
}
}
Related
I'm using VueJS and Cypress. I have a modal where I submit a FormData with different filled and a file:
var formData = new FormData();
formData.append("document_file", this.documentFile);
formData.append("comments", this.comments.value);
...
They way I upload it:
this.$http.post('http://localhost:8081/api/upload',formData,{emulateJSON: true},{
header:{ "Content-Type":"multipart/form-data" },
}).then(function(response) {
// ... Code
}).catch((err) => {
// .. Code
});
}
I want to use Cypress to test the params. Now, when I don't use the document file in the formData, I have the following methods to parse the multipart/form-data; boundary=---:
function parse(request) {
console.log("request");
console.log(request);
const headers = request.headers;
const body = request.body;
const content_type = headers['content-type'];
expect(content_type, 'boundary').to.match(/^multipart\/form-data; boundary=/);
const boundary = content_type.split('boundary=')[1];
const values = p(boundary, body);
return values;
}
function p(boundary, body) {
expect(boundary, 'boundary').to.be.a('string');
expect(body, 'body').to.be.a('string');
const parts = body.split(`--${boundary}`).map((s) => s.trim()).filter((s) => s.startsWith('Content-Disposition: form-data;'));
const result = {};
parts.forEach((part) => {
const lines = part.split(/\r?\n/g);
const key = lines[0].match(/name="(.+)"/)[1];
result[key] = (lines.length >= 2) ? lines[2].trim() : "";
});
return result;
}
Which work great. But when I upload the file, I get a different request.body:
They way I'm trying to test:
cy.get('#createDocument').then((interception) => {
assert.isNotNull(interception);
const values = parse(interception.request);
assert.isTrue(values.comments === "");
});
How can I handle this?
I am uploading files to GraphQL API with plain JS. I've been doing this from the same origin for months now and now am trying to implement the exact same thing externally with NodeJS.
My code looks something like this:
const FormData = require('form-data');
const fs = require('fs')
const axios = require('axios')
const payload = generateRequest(input)
axios.post(apiBaseUrl + "/graphql", payload, {
headers: {...payload.getHeaders()}
}).then(response => {
let res = response.data
if (res.data.triggerPcWorkflow.status === 200) {
console.log("success!")
} else {
console.error(res.data.triggerPcWorkflow.message)
}
})
.catch(err => {
if (err.response) {
console.log(err.response.data);
console.log(err.response.status);
console.log(err.response.headers);
}
})
With the generateRequest function generating the multipart payload (https://github.com/jaydenseric/graphql-multipart-request-spec).
I have two identical versions of the Backend running on localhost:5000 and mycooldomain.com. When setting apiBaseUrl to http://localhost:5000 everything works flawlessly. However just by changing the URL to https://www.mycooldmain.com I get a 400 error thrown at me with { errors: [ { message: 'Must provide query string.' } ] }
BTW: A simple query works with both URLs...
Here is my generateRequest function:
const mutation = `
mutation MyMutation($xyz: String) {
doSomething(myInput: $xyz) {
status
message
}
}
`
let sendData = new FormData();
const fileNull = [];
// map
files = generateRequestInput.files
let map = '{'
for (let i = 0; i < files.length; i++) {
fileNull.push(null);
map += `"${i}": ["variables.files.${i}"], `
}
map = map.substring(0, map.length-2);
map += '}'
// operations
const operations = JSON.stringify({
query: mutation,
variables: {
"xyz": "Hello"
}
});
// build payload
sendData.append("operations", operations)
sendData.append("map", map)
for (let i = 0; i < files.length; i++) {
sendData.append(i, files[i]);
}
return sendData
I know that map looks a bit ugly but thats not the point (unless it is).
Has anyone had a similar problem or knows what my error is here?
Thanks!
I skipped on the axios dependency and implemented the request with FormData directly.
The code below works.
function makeRequest(formData, options) {
formData.submit(options, (err, res) => {
if (err) {
console.error(err.message)
return
} else {
if (res.statusCode < 200 || res.statusCode > 299) {
console.error(`HTTP status code ${res.statusCode}`)
}
const body = []
res.on('data', (chunk) => body.push(chunk))
res.on('end', () => {
const resString = Buffer.concat(body).toString()
console.log(resString)
})
}
})
}
const options = {
host: 'mycooldomain.com',
port: 443,
path: '/graphql',
method: 'POST',
protocol: 'https:'
}
makeRequest(payload, options)
I am trying to upload an array of images using my custom API (Node JavaScript), here I just want to save the file name to my database, and uploading of the file works just fine (the middleware which I created for uploading works just fine because I have tried using the same middleware in the postman).
When it comes to the actual JavaScript file it is not working and shows the following error:
Cast to string failed for value "{ '0': {}, '1': {} }" at path "images", images: Cast to Array failed for value "[ { '0': {}, '1': {} } ]" at path "images"
Here is my code
const createProperty = ['city', 'propertyType', 'propertyName', 'images'];
const newProperty = new Array();
for (var i = 0; i < myids.length; i++) {
newProperty[i] = $(myids[i]).val();
}
newProperty[myids.length] = [document.getElementById('PropertyPhotos').files];
const data = {};
createProperty.forEach((id, index) => {
data[createProperty[index]] = newProperty[index]
})
await createData(data);
// here is the CreateDate function
export const createData = async (data) => {
try {
const res = await axios({
method: 'POST',
url: '/api/v1/properties',
data
});
if (res.data.status === 'success') {
showAlert('success', 'Property Created Successfully');
}
console.log(res.data);
} catch (err) {
showAlert('error', err.response.data.message);
console.log(err.response.data.message);
}
}
It looks like you are using Mongo. Look here and here.
And to send files you need to use form-data, but I don't see it in your code.
const formdata = new FormData();
formdata.append("file", fileInput.files[0], "/filepath.pdf");
I have 2 objects:
const subscription = {
endpoint: "dfksjfklsjkld",
keys: {
pkey: "dfsfsdfsf",
auth: "dfsdfsdfsd"
}
};
const extra = {
email: "dfsdfs",
ip:"231342.342.342.34"
};
I would like to put the extra object inside subscription, so it looks like:
subsciption = {
endpoint: ......
keys: {...},
extra: {
email:....,
ip: .....
}
}
then I need to send it as body of a http request:
const response = await fetch(url, {
method: "PUT",
mode: "no-cors",
cache: "no-cache",
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
},
redirect: "follow",
referrerPolicy: "no-referrer",
body: JSON.stringify(subscription),
});
but I found no matter what I do, I always lose the extra property inside subscription in the process of JSON.stringify().
I know the cause: it's because that the properties in extra object are not enumerable.
So far, I have tried:
1.use the spread:
newSub = {
...subscription,
...extra
}
but the content of newSub will be exactly same with extra, the properties of subscription are all lost.
2.add toJSON function into the place where I generate the extra object
getExtra() : {
.......
return {
city: ipObject.city,
country: ipObject.country_name,
ip: ipObject.ip,
lat: ipObject.latitude,
lng: ipObject.longitude,
org: ipObject.org,
postal: ipObject.postal,
region: ipObject.region,
toJSON: () => {
return this;
}
};
}
no effect at all.
I attach my code here:
async function updateSubscription() {
try {
const allowed = await askForPermission();
if (!allowed) return;
let subscription = await getSubscription();
if (!subscription) return;
// email
const email = getEmail();
if (!email || !validateEmail(email)) {
alert("huh...so how are you going to receive notifications?");
return;
}
// ip
let ipObject = await getIP();
let extra = {};
if (ipObject) {
ipObject.email = email;
extra = ipObject;
} else {
extra.email = email;
}
console.log("extra: ", extra);
// var newSubscription = Object.assign({}, subscription, {extra});
// const newSubscription = {
// ...subscription,
// extra
// };
let newSubscription = subscription;
newSubscription["extra"] = extra;
console.log("new subscription1: ", newSubscription);
console.log("new subscription1 stringified: ", JSON.stringify(newSubscription));
const successful = await saveRegistration(newSubscription);
if (successful) alert("you have successfully subscribed to the DC monitor");
else alert("shit happens, try it later");
} catch (err) {
console.log("updateSubscription() failed: ", err);
}
}
async function getSubscription() {
console.log("try to get subscription");
try {
const swRegistration = await navigator.serviceWorker.ready;
const pushSubscription = await swRegistration.pushManager.getSubscription();
console.log("pushSubscription: ", pushSubscription);
return pushSubscription;
} catch (error) {
console.log("getSubscription() error: ", error);
return null;
}
}
Update
1.Tried 1 more approach:
var newSubscription = Object.assign({}, subscription, {extra});
console.log("subscription: ", newSubscription);
console.log("subscription stringified: ", JSON.stringify(newSubscription));
here is the output screenshot:
2.Also this one:
const newSubscription = {
...subscription,
extra
};
console.log("new subscription: ", newSubscription);
console.log("new subscription stringified: ", JSON.stringify(newSubscription));
And here is the screenshot of output:
3.with string index approach:
let newSubscription = subscription;
newSubscription["extra"] = extra;
console.log("new subscription1: ", newSubscription);
console.log("new subscription1 stringified: ", JSON.stringify(newSubscription));
If mutating subscription is OK, you can just use:
subscription['extra'] = extra;
If you want a new object, you can use:
const subscriptionObject = Object.assign({}, subscription, { extra });
EDIT: Since you are working with the Push API, the properties in PushSubscription are not enumerable. So the subscription object does not behave quite like a normal object, which is why the suggested approaches have not been working.
However, you can serialize the push subscription using PushSubscription.toJSON() first to serialize it to a "normal" object, then use one of the suggested techniques:
subscriptionObject = Object.assign({}, subscription.toJSON(), { extra });
why don't you use like simple assignment of property
let subscription = {..}
const extra = {..}
then
subscription.extra = extra;
it should work
This is a bit hacky, but it looks like we don't know how the PushSubscription object is implemented, and it may not work as you expect...
... however it seems to convert to JSON properly using its own method (according to its API), so you may want to try something like this:
const newSub = { ...JSON.parse(subscription.toJSON()), extra };
Thus, converting it to JSON (using the toJSON method in the Push API) and back to a "normal" javascript object, -then- adding the extra property to it.
Have you tried
newSub = {
...subscription, extra
}
you don't need to spread extra in this case.
sub = JSON.stringify(newSub) should result in: "{"endpoint":"dfksjfklsjkld","keys":{"pkey":"dfsfsdfsf","auth":"dfsdfsdfsd"},"extra":{"email":"dfsdfs","ip":"231342.342.342.34"}}"
Do I need to change the model below to upload images to django restapi?
function axmethod(url, method, options) {
if (options !== undefined) {
var {
params = {}, data = {}
} = options
} else {
params = data = {}
}
return new Promise((resolve, reject) => {
const config = {
}
axios({
url,
method,
params,
data,
}).then(res => {
resolve(res)
}, res => {
reject(res)
})
})
}
please help if I need to add?
I can see two things that might be the key:
In order to upload a image, you need to specify contentType: "multipart/form-data" in your config object
The uploaded data needs to be a FormData object. Let's say you have an object myCurrentData which contains a name string and the file itself. You can transform it using:
const data = new FormData();
Object.entries(myCurrentData).forEach(([key, value]) => data.append(key, value || ""));