Вызов 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;
Другие вопросы по тегам