Выборка: данные POST JSON

562

Я пытаюсь поместить объект JSON, используя fetch .

Из того, что я могу понять, мне нужно прикрепить строковый объект к телу запроса, например:

fetch("/echo/json/",
{
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    method: "POST",
    body: JSON.stringify({a: 1, b: 2})
})
.then(function(res){ console.log(res) })
.catch(function(res){ console.log(res) })

При использовании json echo в jsfiddle я ожидаю увидеть объект, который я отправил ( {a: 1, b: 2}) назад, но этого не происходит - chrome devtools даже не отображает JSON как часть запроса, что означает, что он не отправляется.

бритва
источник
Какой браузер вы используете?
Кшиштоф Сафьяновский
@KrzysztofSafjanowski chrome 42, который должен иметь полную поддержку извлечения
Razor
проверьте эту скрипку jsfiddle.net/abbpbah4/2 какие данные вы ожидаете? потому что запрос get fiddle.jshell.net/echo/json показывает пустой объект. {}
Каушик
@KaushikKishore отредактирован, чтобы уточнить ожидаемый результат. res.json()должен вернуться {a: 1, b: 2}.
Бритва
1
Вы забыли включить jsonсвойство, которое содержит данные, которые вы хотите отправить. Тем не менее, я все bodyравно не лечится правильно. Посмотрите эту скрипку, чтобы увидеть, что задержка в 5 секунд пропускается. jsfiddle.net/99arsnkg Также, когда вы пытаетесь добавить дополнительные заголовки, они игнорируются. Это, вероятно, проблема с fetch()самим собой.
Boombox

Ответы:

600

С async/awaitподдержкой ES2017 вот как можно POSTполучить полезную нагрузку JSON:

(async () => {
  const rawResponse = await fetch('https://httpbin.org/post', {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({a: 1, b: 'Textual content'})
  });
  const content = await rawResponse.json();

  console.log(content);
})();

Не можете использовать ES2017? Смотрите ответ @ vp_art, используя обещания

Однако возникает вопрос о проблеме, вызванной давно исправленной ошибкой Chrome.
Оригинальный ответ следует.

Chrome Devtools даже не показывает JSON как часть запроса

Это реальная проблема здесь , и это ошибка в Chrome Devtools , исправленная в Chrome 46.

Этот код работает нормально - он правильно размещает JSON, его просто не видно.

Я бы ожидал увидеть объект, который я отправил обратно

это не работает, потому что это не правильный формат для эха JSfiddle .

Правильный код является:

var payload = {
    a: 1,
    b: 2
};

var data = new FormData();
data.append( "json", JSON.stringify( payload ) );

fetch("/echo/json/",
{
    method: "POST",
    body: data
})
.then(function(res){ return res.json(); })
.then(function(data){ alert( JSON.stringify( data ) ) })

Для конечных точек, принимающих полезные нагрузки JSON, оригинальный код верен

бритва
источник
16
Для записи, это не публикация полезной нагрузки JSON - это форма post ( x-www-form-urlencoded) с данными JSON в поле с именем json. Таким образом, данные кодируются вдвойне. Чистую статью в формате JSON смотрите в ответе @vp_arth ниже.
mindplay.dk
1
@ mindplay.dk Это не пост x-www-form-urlencoded. Fetch API всегда использует кодирование multipart / form-data для объектов FormData.
JukkaP
@JukkaP Я исправлен. Моим главным вопросом была проблема двойного кодирования.
mindplay.dk
2
Content-Type по-прежнему text / html; charset = iso-8859-1 не знаю, что я делаю неправильно ...
KT Works
3
Чтобы быть в безопасности, было бы хорошо подтвердить, res.okесли код ответа является своего рода ошибкой. Также было бы хорошо иметь .catch()предложение в конце. Я понимаю, что это всего лишь образец фрагмента, но имейте это в виду для использования в реальном мире.
Кен Лион
207

Я думаю, что ваша проблема jsfiddleможет обработать form-urlencodedтолько запрос.

Но правильный способ сделать JSON-запрос - передать правильное jsonтело:

fetch('https://httpbin.org/post', {
  method: 'post',
  headers: {
    'Accept': 'application/json, text/plain, */*',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({a: 7, str: 'Some string: &=&'})
}).then(res=>res.json())
  .then(res => console.log(res));

vp_arth
источник
6
Это правильное решение, точка - все остальные, похоже, запутались в отношении x-www-form-urlencodedvs application/json, либо не соответствуя им, либо дважды заключая JSON в строки, закодированные в URL.
mindplay.dk
Но это не работает для jsfiddle. Итак, я не уверен, что понимаю, почему вы скажете: «Это правильное решение, точка». Разве все остальные не выполняют упаковку, чтобы удовлетворить API /echoмаршрута jsfiddle ?
Адам Бек
69

Из поисковых систем я остановился на этой теме для публикации данных не-json с fetch, поэтому подумал, что добавлю это.

Для не-JSON вам не нужно использовать данные формы. Вы можете просто установить Content-Typeзаголовок application/x-www-form-urlencodedи использовать строку:

fetch('url here', {
    method: 'POST',
    headers: {'Content-Type':'application/x-www-form-urlencoded'}, // this line is important, if this content-type is not set it wont work
    body: 'foo=bar&blah=1'
});

Альтернативный способ построить эту bodyстроку, а не печатать ее, как я делал выше, - это использовать библиотеки. Например, stringifyфункция из query-stringили qsпакетов. Таким образом, используя это будет выглядеть так:

import queryString from 'query-string'; // import the queryString class

fetch('url here', {
    method: 'POST',
    headers: {'Content-Type':'application/x-www-form-urlencoded'}, // this line is important, if this content-type is not set it wont work
    body: queryString.stringify({for:'bar', blah:1}) //use the stringify object of the queryString class
});
Noitidart
источник
2
Большое спасибо за строку запроса, я пытался много раз с JSON.stringify, но ajax не возвращал ответ. но строка запроса добилась цели. Я также обнаружил, что это происходит потому, что fetch создает json для параметров тела вместо создания строки.
Дания,
1
Спасибо, мужик! Это лучший ответ! Вчера я несколько часов бился в стену, пытаясь найти способ отправить «тело» с данными формы из моего веб-приложения на мой сервер ... Одно предложение: $ npm install cors --save Это необходимо, чтобы избавиться от " режим: «без корс» »в запросе« Fetch », см. github.com/expressjs/cors
Александр Чередниченко
Спасибо @AlexanderChednichenko! И спасибо, что поделились этой запиской, интересной, о которой я не знал. :)
Noitidart
1
Спасибо от всего сердца. Вы сэкономили мое время, а также мою жизнь дважды :)
Bafsar
1
Спасибо @bafsar!
Noitidart
42

Потратив несколько раз, реверс-инжиниринг jsFiddle, пытаясь сгенерировать полезную нагрузку - эффект есть.

Пожалуйста, обратите внимание на линию, return response.json();где ответ - это не ответ, а обещание.

var json = {
    json: JSON.stringify({
        a: 1,
        b: 2
    }),
    delay: 3
};

fetch('/echo/json/', {
    method: 'post',
    headers: {
        'Accept': 'application/json, text/plain, */*',
        'Content-Type': 'application/json'
    },
    body: 'json=' + encodeURIComponent(JSON.stringify(json.json)) + '&delay=' + json.delay
})
.then(function (response) {
    return response.json();
})
.then(function (result) {
    alert(result);
})
.catch (function (error) {
    console.log('Request failed', error);
});

jsFiddle: http://jsfiddle.net/egxt6cpz/46/ && Firefox> 39 && Chrome> 42

Кшиштоф Сафьяновский
источник
Почему 'x-www-form-urlencodedвместо application/json? Какая разница?
Хуан Пикадо
@JuanPicado - после обратного инжиниринга jsfiddle 2 года назад это был только один вариант, который мог работать. Конечно, application/jsonэто правильная форма, и она работает сейчас. Спасибо за хороший глаз
:)
уш. Любопытная деталь, она работает для меня по-старому с fetch( stackoverflow.com/questions/41984893/… ) вместо application/json. Возможно, вы знаете, почему ...
Хуан Пикадо
6
Content-TypeЕсть application/json, но фактические , bodyкажется, x-www-form-urlencoded- я не думаю , что это должно работать? Если это работает, ваш сервер должен быть довольно щадящим. Ответ @vp_arth ниже выглядит правильным.
mindplay.dk
18

Я создал тонкую оболочку для fetch () со многими улучшениями, если вы используете исключительно JSON REST API:

// Small library to improve on fetch() usage
const api = function(method, url, data, headers = {}){
  return fetch(url, {
    method: method.toUpperCase(),
    body: JSON.stringify(data),  // send it as stringified json
    credentials: api.credentials,  // to keep the session on the request
    headers: Object.assign({}, api.headers, headers)  // extend the headers
  }).then(res => res.ok ? res.json() : Promise.reject(res));
};

// Defaults that can be globally overwritten
api.credentials = 'include';
api.headers = {
  'csrf-token': window.csrf || '',    // only if globally set, otherwise ignored
  'Accept': 'application/json',       // receive json
  'Content-Type': 'application/json'  // send json
};

// Convenient methods
['get', 'post', 'put', 'delete'].forEach(method => {
  api[method] = api.bind(null, method);
});

Для его использования у вас есть переменная apiи 4 метода:

api.get('/todo').then(all => { /* ... */ });

И внутри asyncфункции:

const all = await api.get('/todo');
// ...

Пример с jQuery:

$('.like').on('click', async e => {
  const id = 123;  // Get it however it is better suited

  await api.put(`/like/${id}`, { like: true });

  // Whatever:
  $(e.target).addClass('active dislike').removeClass('like');
});
Франциско Презенсия
источник
Я думаю, что вы имели в виду другой набор аргументов Object.assign? должно быть Object.assign({}, api.headers, headers)потому, что вы не хотите продолжать добавлять пользовательский headersв общий хеш api.headers. правильно?
Mobigital
@ Совершенно верно, я не знал об этом нюансе тогда, но теперь это единственный способ сделать это
Francisco Presencia
11

Это связано с Content-Type. Как вы могли заметить из других обсуждений и ответов на этот вопрос, некоторые люди смогли решить его, установив Content-Type: 'application/json'. К сожалению, в моем случае это не сработало, мой POST-запрос все еще был пустым на стороне сервера.

Однако, если вы попробуете с jQuery, $.post()и он работает, причина, вероятно, в том, что Content-Type: 'x-www-form-urlencoded'вместо jQuery используется application/json.

data = Object.keys(data).map(key => encodeURIComponent(key) + '=' + encodeURIComponent(data[key])).join('&')
fetch('/api/', {
    method: 'post', 
    credentials: "include", 
    body: data, 
    headers: {'Content-Type': 'application/x-www-form-urlencoded'}
})
Маркус Линд
источник
1
Мой бэкэнд dev разработал API с PHP, ожидал, что данные будут похожи на строку запроса, а не на объект json. Это решило пустой ответ на стороне сервера.
eballeste
11

Была такая же проблема - нет bodyбыло отправлено с клиента на сервер.

Добавление Content-Typeзаголовка решило это для меня:

var headers = new Headers();

headers.append('Accept', 'application/json'); // This one is enough for GET requests
headers.append('Content-Type', 'application/json'); // This one sends body

return fetch('/some/endpoint', {
    method: 'POST',
    mode: 'same-origin',
    credentials: 'include',
    redirect: 'follow',
    headers: headers,
    body: JSON.stringify({
        name: 'John',
        surname: 'Doe'
    }),
}).then(resp => {
    ...
}).catch(err => {
   ...
})
зеленый
источник
7

Верхний ответ не работает для PHP7, потому что он имеет неправильную кодировку, но я мог бы найти правильную кодировку с другими ответами. Этот код также отправляет куки аутентификации, которые вы, вероятно, хотите иметь, например, на PHP-форумах:

julia = function(juliacode) {
    fetch('julia.php', {
        method: "POST",
        credentials: "include", // send cookies
        headers: {
            'Accept': 'application/json, text/plain, */*',
            //'Content-Type': 'application/json'
            "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" // otherwise $_POST is empty
        },
        body: "juliacode=" + encodeURIComponent(juliacode)
    })
    .then(function(response) {
        return response.json(); // .text();
    })
    .then(function(myJson) {
        console.log(myJson);
    });
}
lama12345
источник
3

Это может быть полезно для кого-то:

У меня была проблема, что формальные данные не отправлялись для моего запроса

В моем случае это была комбинация следующих заголовков, которые также вызывали проблему и неправильный Content-Type.

Таким образом, я отправлял эти два заголовка с запросом, и он не отправлял форм-данные, когда я удалял работающие заголовки.

"X-Prototype-Version" : "1.6.1",
"X-Requested-With" : "XMLHttpRequest"

Также как и другие ответы предполагают, что заголовок Content-Type должен быть правильным.

Для моего запроса правильный заголовок Content-Type был:

"Content-Type": "application / x-www-form-urlencoded; charset = UTF-8"

Итак, суть в том, что ваши форм-данные не привязаны к запросу, тогда это могут быть ваши заголовки. Попробуйте свести заголовки к минимуму, а затем попробуйте добавить их один за другим, чтобы увидеть, решена ли ваша проблема.

user_CC
источник
3

Я думаю, что нам не нужно анализировать объект JSON в строку, если удаленный сервер принимает json в свой запрос, просто запустите:

const request = await fetch ('/echo/json', {
  headers: {
    'Content-type': 'application/json'
  },
  method: 'POST',
  body: { a: 1, b: 2 }
});

Такие как запрос curl

curl -v -X POST -H 'Content-Type: application/json' -d '@data.json' '/echo/json'

Если удаленная служба не принимает файл json в качестве тела, просто отправьте dataForm:

const data =  new FormData ();
data.append ('a', 1);
data.append ('b', 2);

const request = await fetch ('/echo/form', {
  headers: {
    'Content-type': 'application/x-www-form-urlencoded'
  },
  method: 'POST',
  body: data
});

Такие как запрос curl

curl -v -X POST -H 'Content-type: application/x-www-form-urlencoded' -d '@data.txt' '/echo/form'
Даниэль Гарсия
источник
2
Это явно неверно. Это не имеет ничего общего с серверной стороной, нужно ли вам преобразовывать свой JSON в строку. Это именно то, что ваша curlкоманда делает неявно! Если вы не упорядочиваете свои объекты перед тем, как передать их, bodyвы просто отправите их "[object Object]"как тело запроса. Простой тест в Dev Tools покажет вам это. Откройте его и попробуйте, не выходя из этой вкладки:a = new FormData(); a.append("foo","bar"); fetch("/foo/bar", { method: 'POST', body: {}, headers: { 'Content-type': 'application/json' } })
oligofren
2

Если ваша полезная нагрузка JSON содержит массивы и вложенные объекты, я бы использовал метод URLSearchParams jQuery param().

fetch('/somewhere', {
  method: 'POST',
  body: new URLSearchParams($.param(payload))
})

Чтобы ваш сервер, это будет выглядеть как стандартный HTML <form>существо под POSTред.

Эрик Селлин
источник