Вызов jQuery ajax не может читать данные в кодировке json из CakePHP 3.8 (получает пустой массив)
У меня странная проблема с чтением данных в кодировке json, возвращаемых моим API CakePHP3 в ответ на вызов ajax из jQuery. Я уже прочитал более 20 сообщений о stackru и других местах, а также об обычных проблемах, с которыми сталкивались люди из-за неправильного dataType, contentType или из-за того, что сервер не получал данные из ajax. Ни один из этих случаев здесь не применим (я пробовал другие настройки, но это не повлияло на мою проблему).
Проблема:
Мой вызов ajax отправляет некоторые параметры моему API CakePHP3, API получает параметры правильно и возвращает массив объектов CakePHP в кодировке json (каждый объект имеет дополнительное свойство available_yield, добавленное перед отправкой обратно в вызов ajax). Я получаю правильный вывод, используя прямой URL-адрес в браузере (проверил его с помощью валидаторов json, все в порядке), но мой вызов ajax (для исследования я использовал вкладки консоли и сети в Chrome devtools) показывает пустой массив для правильно сформированного json.
Мое исследование показало, что проблема возникает, когда я изменяю объекты CakePHP. Если я верну исходные данные из json-кодированного API, jquery ajax получит правильные данные. Но когда я изменяю какой-либо объект, массив в jquery ajax пуст.
Отладка из CakePHP показывает, что оба массива (немодифицированный и измененный) выглядят точно так же, за исключением добавленного свойства, то есть они правильно сформированы и в порядке во всех отношениях, оба находятся в json, оба в порядке в браузере. Но модифицированный не принимается jquery как json.
На данный момент решение кажется таким: не изменяйте свои данные! Но это то, что мы делаем на сервере перед отправкой соответствующих и обработанных данных клиенту, не так ли?
У кого-нибудь была подобная проблема?
Прилагаю свой код:
Функция API CakePHP:
function myFunction(){
$params = $this->getRequest()->getQueryParams();
//debug($params);
$componentReference = $params['component_reference'];
$componentTypeId = $params['component_type_id'];
$matchingCrops = $this->Crops->find()->select(['id', 'grower_name', 'bulk'])->where(['reference' => $componentReference]);
$cropsWithYieldInfo = []; //to hold modify crop
foreach($matchingCrops as $crop){
$availableYield = $this->Crops->calculateAvailableYield($crop->id); //returns a string
if(isset($availableYield) && !empty($availableYield)){
$crop->available_yield = number_format($availableYield,1); //tried $crop['available_yield'] as well, same result
$cropsWithYieldInfo[] = $crop;
}
}
// debug($cropsWithYieldInfo);
// debug($matchingCrops);
//$content = json_encode($cropsWithYieldInfo); // <<-- changing to $matchingCrops makes ajax see the array, but the array does not have my calculated data
$content = json_encode($matchingCrops);
$this->response = $this->response->withStringBody($content);
$this->response = $this->response->withType('json');
$this->autoRender = false;
return $this->response;
}
мой AJAX:
function myAjax(){
$.ajax({
type: 'GET',
url: url,
//contentType: "application/json",
dataType: "json"
})
.done(function (data) {
console.log(data);
})
.fail(function (data) {
console.log('AJAX call to /'+errMsg+' function failed');
})
}
Данные JSON, возвращенные API:
РЕДАКТИРОВАТЬ:Может быть важно: когда я обращаюсь к API через URL-адрес в браузере, он всегда возвращает измененные данные; похоже, что мой код изменяет фактические сущности в наборе $ matchCrops. Поэтому, если для $ content установлено значение $ MatchCrops или $CropsWithYieldInfo, результат в браузере всегда будет одинаковым. Но это отличается при доступе к API через ajax: когда $content = json_encoded($ MatchCrops) я получаю исходный немодифицированный массив данных, когда $content = json_encoded($CropsWithYieldInfo) я получаю пустой массив.
Это действительно странно: почему браузер всегда получает модифицированный массив, а ajax получает либо тот, либо другой??? Я понимаю, что если я изменяю объект $ crop, он изменяет объект внутри результирующего набора, но я ожидал, что это будет согласовано как для браузера, так и для вызова ajax.
РЕДАКТИРОВАТЬ: Я попробовал немного измененный код, чтобы увидеть, будет ли клонирование сущностей иметь какое-либо значение, но единственная разница в том, что теперь браузер получает то, что я ожидал (либо исходный немодифицированный массив, либо модифицированный), и он соответствует тому, что ajax получает. Но это не решает проблему (ajax по-прежнему получает пустой массив, если массив был изменен).
foreach($matchingCrops as $crop){
$modCrop = clone $crop;
$availableYield = $this->Crops->calculateAvailableYield($crop->id); //returns a string
if(isset($availableYield) && !empty($availableYield)){
$modCrop->available_yield = number_format($availableYield,1); //tried $crop['available_yield'] as well, same result
$cropsWithYieldInfo[] = $modCrop;
}
}
Изменено (ajax получает это как пустой массив; браузер всегда получает это из API):
[{"id":12345,"grower_name":"XYZ","bulk":false,"available_yield":"4.1"},{"id":23456,"grower_name":null,"bulk":true,"available_yield":"190.0"}]
Без изменений (ajax правильно понимает):
[{"id":12345,"grower_name":"XYZ","bulk":false},{"id":23456,"grower_name":null,"bulk":true}]
2 ответа
Боже... нашел! Хорошо, это неловко, но я все равно опубликую это как ИЗВЛЕЧЕННЫЙ УРОК и как предупреждение другим людям: если у вас вечером возникнет проблема, которую вы не можете решить, идите домой и хорошо выспитесь, начните снова в утро!
Причины проблемы:
1) моя функция вычисления фактически возвращала число с плавающей запятой, а не строку, и я проверял пустоту, поэтому, когда она вернула 0, код не добавлял свойство 'available_yield' к объекту $crop (потому что строка кода, отвечающая за это тоже было не в том месте! должно было быть за пределами блока if)
В этот момент я все еще говорил: "Хорошо, но я должен добиться согласованного поведения как в браузере, так и в вызове ajax!!!", если только...
2) Я не заметил, что я использовал другой идентификатор для проверки браузера и для вызова ajax, поэтому компьютер был прав...:-/
Всегда учусь...
- перепроверьте каждую строку и отладьте все возможные переменные!
- перепроверьте свои ТЕСТОВЫЕ ДАННЫЕ!
Версия кода, которая работает нормально:
function myFunction(){
$params = $this->getRequest()->getQueryParams();
//debug($params);
$componentReference = $params['component_reference'];
$componentTypeId = $params['component_type_id'];
$matchingCrops = $this->Crops->find()->select(['id', 'grower_name', 'bulk'])->where(['reference' => $componentReference]);
$cropsWithYieldInfo = []; //to hold modify crop
$cropsWithYieldString = '';
foreach($matchingCrops as $crop){
$availableYield = $this->Crops->calculateAvailableYield($crop->id); //returns a float not string!
if(isset($availableYield)){ //<<- that was the cause of the problem; !empty(float) will ignore 0, just check if it's set
$crop->available_yield = number_format($availableYield,1);
}
$cropsWithYieldInfo[] = $crop;
}
// debug($cropsWithYieldInfo);
// debug($matchingCrops);
$content = json_encode($cropsWithYieldInfo);
//$this->response = $this->response->withStringBody($content);
//$this->response = $this->response->withType('application/json');
$this->autoRender = false;
//return $this->response;
//more concisely
return $this->response->withType('application/json')->withStringBody($content);
}
Спасибо за ваше время, ребята, вы заставили меня сосредоточиться на поиске решения.
$array = ['foo'=>'bar'];
$this->set([
'response' => $array,
'_serialize' => 'response',
]);
$this->Request->renderAs($this, 'json');
И чем я бы серилиазировал ajax! Таким образом, вам не нужно преобразовывать объект в строку, вы можете использовать его непосредственно для свойства данных.
$.ajax({
type: 'POST',
url: url,
data: {YourArray: YourVariables},
success: function(data) {
alert(data);
}
});
Вы можете найти больше здесь: https://api.jquery.com/serialize/
Давайте посмотрим на ваш ajax, вам нужно передать значения из массива в ajax, чтобы получить ответ каждого значения, которое вы пытаетесь сделать. +errMsg+
.
Ваш ajax должен выглядеть так при неудаче и успехе:
потерпеть поражение: function( jqXHR, Status, errMsg) {
тогда вы можете показать ответ как console.log('AJAX call to /'+errMsg+' function failed');
$.ajax({
type: "GET",
url: url,
data: {
title: $(value[0]).val(),
description: $(value[1]).val()
},
success: function (data) {
if(data === "success") {
// do something with data or whatever other data on success
console.log('success');
} else if(data === "error") {
// do something with data or whatever other data on error
console.log('error');
}
}
});
Чтобы показать указанную ошибку, вам необходимо пройти title = $(value[0]).val()
в функции успеха.
Или используйте ajax serializeArray()
а также each()
пример здесь https://www.w3schools.com/jquery/tryit.asp?filename=tryjquery_ajax_serializearray
Начиная с CakePHP 3.4, следует использовать
$content = json_encode($matchingCrops);
return $this->response->withType("application/json")->withStringBody($content);
Вместо этого
$content = json_encode($matchingCrops);
$this->response = $this->response->withStringBody($content);
$this->response = $this->response->withType('json');
$this->autoRender = false;
return $this->response;