Вызов jQuery ajax не может прочитать данные в кодировке json из CakePHP 3.8 (получает пустой массив)

У меня странная проблема с чтением закодированных данных json, возвращаемых моим API CakePHP3 в ответ на вызов ajax из jQuery. Я уже прочитал более 20 сообщений в stackoverflow и других местах, а также обычные проблемы, с которыми люди сталкивались, когда из-за неправильного типа данных, типа контента или из-за того, что сервер не получал данные от ajax. Ни один из этих случаев здесь не применим (я пробовал разные настройки, но это не повлияло на мою проблему).

Проблема:

Мой вызов ajax отправляет некоторые параметры моему API CakePHP3, API правильно получает параметры и возвращает закодированный json массив объектов CakePHP (каждый объект имеет дополнительное свойство «доступный_выход», добавленное перед его отправкой обратно в вызов ajax). Я получаю правильный вывод, используя прямой URL-адрес в браузере (проверил его с помощью валидаторов json, все хорошо), но мой вызов ajax (для исследования я использовал вкладки консоли и сети в инструментах разработки Chrome) показывает пустой массив для правильно сформированного json.

Мое расследование показало, что проблема возникает, когда я изменяю сущности CakePHP. Если я верну исходные данные из API в кодировке json, 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-адрес в браузере, он всегда возвращает измененные данные; похоже, мой код изменяет фактические объекты в наборе $matchingCrops. Таким образом, если установить для $content значение $matchingCrops или $cropsWithYieldInfo, результат в браузере всегда будет одинаковым. Но отличается при доступе к API через ajax: когда $content = json_encoded($matchingCrops) я получаю исходный немодифицированный массив данных, когда $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}]


person djevulen    schedule 06.02.2020    source источник
comment
Простой вопрос - лучший вопрос!   -  person Dlk    schedule 06.02.2020
comment
Контроллеры никогда не должны отображать данные, это только вызовет проблемы!   -  person ndm    schedule 07.02.2020
comment
@ndm Я могу вернуть ответ (вместо повторения данных), но это ничего не меняет в моей ситуации. Но спасибо за общий совет.   -  person djevulen    schedule 07.02.2020
comment
@Dlk Я бы хотел, чтобы это было просто ... Я до сих пор не понимаю, почему у меня такое поведение или как упростить проблему.   -  person djevulen    schedule 07.02.2020
comment
@djevulen Дело в том, что это может не только создать (дополнительные) проблемы для вас, это также проблема для людей, пытающихся помочь, поскольку неясно, что на самом деле может вызвать такое поведение, и вещи могут вести себя по-разному в локальной среде людей при эхо. в отличие от вашей среды, поэтому, даже если это, казалось бы, не имеет значения для вас, рекомендуется предоставить и использовать пример, который делает это предполагаемым образом, чтобы возможные источники проблем были как можно меньше .   -  person ndm    schedule 07.02.2020
comment
Вы правильно загружаете модель внутри своего контроллера? Возможно, модель не имеет отношения и поэтому не может найти никакой информации.   -  person Oris Sin    schedule 07.02.2020
comment
@ndm Большое спасибо за это. Я прочитал страницу, на которую вы ссылаетесь, и это имеет смысл. Я изменил свой код и разместил его выше.   -  person djevulen    schedule 07.02.2020
comment
@Orin Sin: Да, моя модель загружена, и я получаю правильные данные в браузере и внутри контроллера. Я не могу получить их через ajax, когда они изменены, но могу, когда они не изменены.   -  person djevulen    schedule 07.02.2020
comment
Вы пробовали return $this->response->withType("application/json")->withStringBody(json_encode($result)); вместо return $this->response;   -  person Dlk    schedule 07.02.2020
comment
@Dlk Да, я сделал. Несколько минут назад я изменил свой код в вопросе с этими настройками. Он показывает $this-›response-›withType('json'), но я также попробовал 'application/json' с тем же результатом.   -  person djevulen    schedule 07.02.2020


Ответы (2)


$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 должен выглядеть так при неудаче и успехе:

fail: 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;
person Dlk    schedule 07.02.2020
comment
Что ж, источник проблемы в данном случае был между клавиатурой и стулом ;) но спасибо за ответ. Мне нравится ваш краткий ответ $this-›... и я изменил свой код, чтобы сохранить строки. - person djevulen; 07.02.2020
comment
@djevulen Я прокомментировал это в своем комментарии под вопросом 1 час назад, я думаю :) вы потратили 1 час впустую :) в любом случае! рад видеть, что вы решили. - person Dlk; 07.02.2020
comment
Ваш комментарий к вопросу 1 не был решением проблемы. На самом деле результат, который я получаю, одинаков как с вашим кодом, так и с моим. Ваш более лаконичен, в этом главное отличие. - person djevulen; 07.02.2020

ОМГ...нашла! Хорошо, это смущает, но я все равно опубликую это как ВЫУЧЕННЫЙ УРОК и как предупреждение другим людям: если у вас есть проблема вечером, которую вы не можете решить, идите домой и хорошо выспитесь, начните снова утром!

Причины проблемы:

1) моя функция вычисления на самом деле возвращала число с плавающей запятой, а не строку, и я проверял на пустоту, поэтому, когда она возвращала 0, код не добавлял свойство «доступный_выход» к объекту $crop (поскольку строка кода, отвечающая за это тоже было не в том месте! должно было быть за пределами блока if)

В этот момент я все еще думал: «Хорошо, но я должен получить стабильное поведение как в браузере, так и при вызове ajax !!!», если только...

2) Я не заметил, что использовал другой id для проверки браузера и для вызова 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);

}

Спасибо за ваше время, ребята, вы заставили меня сосредоточиться на поиске решения.

person djevulen    schedule 07.02.2020
comment
Вы должны подождать две минуты :) - person Dlk; 07.02.2020
comment
@Dlk Прочитал твой ответ. Спасибо, что указали, что мой метод отказа ajax может выиграть от функции (jqXHR, Status, errMsg) :) - person djevulen; 07.02.2020