Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Drennie's avatar

Strange behavior: order preservation in collection after json_encode

Hello everyone,

First of all I have managed to solve the problem in another way, but still I am curious what is happening.

So let's say I have a collection of races. Each race has equipment of type recommended or mandatory. I want to sort the equipment by type and , because it makes a lot more sense in a list view to separate the two easily.

I tried groupBy:

        foreach ($races as &$race) {
            $race['equipmentables'] = $race['equipmentables']->groupBy('type');
        }
        unset($race);

I have also tried sortBy with different combinations of array_values(), collect(), values(), toArray() without success.

        foreach ($races as &$race) {
            $race['equipmentables'] = array_values($race['equipmentables']->sortBy('type')->values()->toArray());
            error_log(gettype($race['equipmentables']));
        }
        unset($race);

error_log(json_encode($races[1]['equipmentables']));

The error_log confirms that everything is good.

Unfortunately it didn't show on the website view, so now I'm testing with the following:

        $data['test2'] =  collect($races[1]['equipmentables'])->sortBy('type')->values()->toArray();
        $data['test3'] =  $races[1]['equipmentables'];
        $data['test4'] =  $races[1];

test2 and test3 preserve the order, test4 doesn't. But test 3 is just part of test4. What happened?

test2 and test 3:
[
    {
        "id": 1,
    },
    {
        "id": 7,
    },
    {
        "id": 8,
    },
    {
        "id": 9,
    },
    {
        "id": 10,
    },
    {
        "id": 11,
    },
    {
        "id": 12,
    },
    {
        "id": 13,
    },
    {
        "id": 14,
    },
    {
        "id": 15,
    },
    {
        "id": 16,
    },
    {
        "id": 17,
    },
    {
        "id": 18,
    },
    {
        "id": 19,
    },
    {
        "id": 2,
    },
    {
        "id": 3,
    },
    {
        "id": 4,
    },
    {
        "id": 5,
    },
    {
        "id": 6,
    }
]

vs test 4

[
    {
        "id": 1,
    },
    {
        "id": 2,
    },
    {
        "id": 3,
    },
    {
        "id": 4,
    },
    {
        "id": 5,
    },
    {
        "id": 6,
    },
    {
        "id": 7,
    },
    {
        "id": 8,
    },
    {
        "id": 9,
    },
    {
        "id": 10,
    },
    {
        "id": 11,
    },
    {
        "id": 12,
    },
    {
        "id": 13,
    },
    {
        "id": 14,
    },
    {
        "id": 15,
    },
    {
        "id": 16,
    },
    {
        "id": 17,
    },
    {
        "id": 18,
    },
    {
        "id": 19,
    }
]

Why does the order change in test4 and not in the others?

I've read up on json_encode and JS not preserving order, but I can't see what's the difference between $data['test3'] = $races[1]['equipmentables']; $data['test4'] = $races[1];

var_dump gives the same strange results.

var_dump($races[1]); is fine. var_dump(json_encode($races[1])); is wrong, with or without ->toArray().

What do you think?

In addition

0 likes
3 replies
rodrigo.pedra's avatar

Can you share a sample of the original dataset?

It is somewhat challenging to follow from your description, without seeing the input data.

Drennie's avatar

It's a lot of data so I will have to cut it.

I have tried to reproduce the issue with this custom code, but that seems fine. So something is wrong with the data it seems.

        $fruits = collect([
            [
                'name' => 'Apple',
                'type' => 'Fruit',
            ],
            [
                'name' => 'Orange',
                'type' => 'Citrus',
            ],
            [
                'name' => 'Banana',
                'type' => 'Fruit',
            ],
            // Add more fruits as needed
        ]);

        $greenProduce = [
            'vegetables' => [
                'Broccoli',
                'Spinach',
                'Kale',
                // Add more vegetables as needed
            ],
            'fruits' => $fruits,
            // Add more fruits as needed
        ];

        $fruitCollection = collect($greenProduce);

        // You can now work with the $fruitCollection, which is an instance of the Illuminate\Support\Collection class.

        // For example, you can filter fruits by type:
        $fruitType = 'Fruit';
        $filteredFruits = $fruitCollection->where('type', $fruitType);

        $sortedFruit = $fruitCollection['fruits']->groupBy('type');

        $fruitCollection['fruits'] = $sortedFruit;

        error_log($sortedFruit);
        error_log($fruitCollection['fruits']);
        error_log($fruitCollection);
        error_log(json_encode($fruitCollection['fruits']));
        error_log(json_encode($fruitCollection));
Drennie's avatar

error_log($races[1]['equipmentables']);outputs correctly groupedBy(type )to:


{
  "mandatory": [
    {
      "id": 1,
      "created_at": "2023-12-04T15:22:22.000000Z",
      "updated_at": "2023-12-04T15:22:22.000000Z",
      "equipment_id": 1,
      "race_id": 1,
      "type": "mandatory",
      "equipment": {
        "id": 1,
        "created_at": "2023-12-04T15:22:21.000000Z",
        "updated_at": "2023-12-04T15:22:21.000000Z",
        "handle": "faceMask",
        "name": null
      }
    },
    {
      "id": 7,
      "created_at": "2023-12-04T15:22:22.000000Z",
      "updated_at": "2023-12-04T15:22:22.000000Z",
      "equipment_id": 7,
      "race_id": 1,
      "type": "mandatory",
      "equipment": {
        "id": 7,
        "created_at": "2023-12-04T15:22:21.000000Z",
        "updated_at": "2023-12-04T15:22:21.000000Z",
        "handle": "backpack",
        "name": null
      }
    },
    {
      "id": 8,
      "created_at": "2023-12-04T15:22:22.000000Z",
      "updated_at": "2023-12-04T15:22:22.000000Z",
      "equipment_id": 8,
      "race_id": 1,
      "type": "mandatory",
      "equipment": {
        "id": 8,
        "created_at": "2023-12-04T15:22:21.000000Z",
        "updated_at": "2023-12-04T15:22:21.000000Z",
        "handle": "mobile",
        "name": null
      }
    },
    // ... (similar blocks for other mandatory items)
  ],
  "recommended": [
    {
      "id": 2,
      "created_at": "2023-12-04T15:22:22.000000Z",
      "updated_at": "2023-12-04T15:22:22.000000Z",
      "equipment_id": 2,
      "race_id": 1,
      "type": "recommended",
      "equipment": {
        "id": 2,
        "created_at": "2023-12-04T15:22:21.000000Z",
        "updated_at": "2023-12-04T15:22:21.000000Z",
        "handle": "poles",
        "name": null
      }
    },
    {
      "id": 3,
      "created_at": "2023-12-04T15:22:22.000000Z",
      "updated_at": "2023-12-04T15:22:22.000000Z",
      "equipment_id": 3,
      "race_id": 1,
      "type": "recommended",
      "equipment": {
        "id": 3,
        "created_at": "2023-12-04T15:22:21.000000Z",
        "updated_at": "2023-12-04T15:22:21.000000Z",
        "handle": "knife",
        "name": null
      }
    },
    // ... (similar blocks for other recommended items)
  ]
}

While error_log($races[1]); outputs to:

{
   "id":1,
   "published_at":null,
   "primary_color":"slate-800",
   "secondary_color":"red-500",
   "featured_image_id":null,
   "featured_video_id":null,
   "panorama_id":21,
   "medical_certificate":"mandatory",
   "require_livetracking":1,
   "program_id":null,
  "equipmentables": [
   {
      "id": 1,
      "created_at": "2023-12-04T15:22:22.000000Z",
      "updated_at": "2023-12-04T15:22:22.000000Z",
      "equipment_id": 1,
      "race_id": 1,
      "type": "mandatory",
      "equipment": {
         "id": 1,
         "created_at": "2023-12-04T15:22:21.000000Z",
         "updated_at": "2023-12-04T15:22:21.000000Z",
         "handle": "faceMask",
         "name": null
      }
   },
   {
      "id": 2,
      "created_at": "2023-12-04T15:22:22.000000Z",
      "updated_at": "2023-12-04T15:22:22.000000Z",
      "equipment_id": 2,
      "race_id": 1,
      "type": "recommended",
      "equipment": {
         "id": 2,
         "created_at": "2023-12-04T15:22:21.000000Z",
         "updated_at": "2023-12-04T15:22:21.000000Z",
         "handle": "poles",
         "name": null
      }
   },
   {
      "id": 3,
      "created_at": "2023-12-04T15:22:22.000000Z",
      "updated_at": "2023-12-04T15:22:22.000000Z",
      "equipment_id": 3,
      "race_id": 1,
      "type": "recommended",
      "equipment": {
         "id": 3,
         "created_at": "2023-12-04T15:22:21.000000Z",
         "updated_at": "2023-12-04T15:22:21.000000Z",
         "handle": "knife",
         "name": null
      }
   },...
],
   "start_date":"2023-05-27",
   "start_time":"10:00",
   "max_duration":"4d, 2h",
   "highlights":[
      // (highlight entries)
   ],
   "ruleables":[
      // (ruleables entries)
   ]
}

So in short: the groupBy and sortBy effect is invisible when the complete object $races[1] is shown, but when $races[1]['equipmentables'] is called specifically, it is shown correctly.

Please or to participate in this conversation.