Skip to content

Sub Document - Array Collection Date and _id format. #1426

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
janzell opened this issue Feb 7, 2018 · 12 comments
Closed

Sub Document - Array Collection Date and _id format. #1426

janzell opened this issue Feb 7, 2018 · 12 comments

Comments

@janzell
Copy link

janzell commented Feb 7, 2018

Hey guys,

I have a problem with the date and _id format for embedded documents which has an array of object collection values.

So for example, if I have a Client model which has an embedMany relationship to an embedded document called employee (the employee is an array of objects embedded in a client model/document). See the example data below.

{ "_id": "5a7a624bb9dfdc00a35c4669", "name": "jo3qmldbuglcsmdgkcoc", "label": "Beier Group", "enabled": false, "logo_image_id": "6d401890-a561-362a-b829-97cd3d20776e", "created_at": "1987-07-28 19:33:55", "updated_at": "2016-12-09 22:58:56", "product_id": "5a7a624bb9dfdc00a35c4665", "employees": [ { "user_id": 931, "name": "Gregory Stark", "profile_image_id": "20c07f90-1846-329c-9544-30c45407b4ef", "created_at": { "$date": { "$numberLong": "1490943600000" } }, "updated_at": { "$date": { "$numberLong": "1517969995000" } }, "_id": { "$oid": "5a7a624bb9dfdc00a35c4673" } }, { "user_id": 249, "name": "Mrs. Rhoda Orn", "profile_image_id": "b3e498de-ba91-325e-8e17-8258bdbb4362", "created_at": { "$date": { "$numberLong": "1490943600000" } }, "updated_at": { "$date": { "$numberLong": "1517969995000" } }, "_id": { "$oid": "5a7a624bb9dfdc00a35c4674" } } ] }

Now the problem is that the _id and date of the embedded document (employee) when getting the client collection was formatted as MongoDB\BSON\ObjectId and object(MongoDB\BSON\UTCDateTime) respectively instead of a string.

This is only happening if the embedded document with an array of objects format. I'm guessing it is because of the Jenssegers\Mongodb\Eloquent\Model.php which only supports the first level of dot notation as you can see below. Does anyone having the same issue like this?

public function attributesToArray()
    {
        $attributes = parent::attributesToArray();

        // Because the original Eloquent never returns objects, we convert
        // MongoDB related objects to a string representation. This kind
        // of mimics the SQL behaviour so that dates are formatted
        // nicely when your models are converted to JSON.
        foreach ($attributes as $key => &$value) {
            if ($value instanceof ObjectID) {
                $value = (string) $value;
            }
        }

        // Convert dot-notation dates.
        foreach ($this->getDates() as $key) {
            if (Str::contains($key, '.') && Arr::has($attributes, $key)) {
                Arr::set($attributes, $key, (string) $this->asDateTime(Arr::get($attributes, $key)));
            }
        }

        return $attributes;
    }
@janzell janzell changed the title Sub Document - Array Collection Date format. Sub Document - Array Collection Date and _id format. Feb 7, 2018
@VarroReve
Copy link

i have the same problem.

@Siebov
Copy link

Siebov commented Apr 5, 2019

Any updates on this? @jenssegers

When selecting as single doc, dates displays as a date

        {
            "_id": "5ca71ce0d542352064004034",
            "title": "General review",
            "status": "active",
            "start_date": "2019-04-05 09:16:16.000000",
            "end_date": "2038-01-19 03:14:07.000000",
            "start_review_date": "2019-04-05 09:16:16.000000",
            "submission_fee": 100,
            "reward": 0,
            "is_general_review_round": true,
            "created_at": "2019-04-05 09:16:16.000000",
            "updated_at": "2019-04-05 09:16:16.000000",
            "proposals": [],
            "demography": null
        },

But on select as related object I have this date conversion issue...

{
                    "title": "Omnis perspiciatis aut voluptatem dolores vel repudiandae.",
                    "start_date": {
                        "$date": {
                            "$numberLong": "1554542206000"
                        }
                    },
                    "end_date": {
                        "$date": {
                            "$numberLong": "1556183806000"
                        }
                    },
                    "start_review_date": {
                        "$date": {
                            "$numberLong": "1555406206000"
                        }
                    },
                    "updated_at": {
                        "$date": {
                            "$numberLong": "1554455806312"
                        }
                    },
                    "created_at": {
                        "$date": {
                            "$numberLong": "1554455806312"
                        }
                    }
                }

@Siebov
Copy link

Siebov commented Apr 5, 2019

@janzell @SakyaVarro guys, have you found solution?

@laurentlemaire
Copy link

Any update on this issue?

@glowens
Copy link

glowens commented Dec 14, 2019

Any update on this?

@leong918
Copy link

same here issue return json data format issue
[ { "_id": "5e0aecc7439f1e1b8cf1b432", "members": [ 1, 3 ], "messages": [ { "_id": { "$oid": "5e0aece4439f1e1b8cf1b433" }, "user_id": 3, "text": "123", "created_at": { "$date": { "$numberLong": "1577774308193" } }, "updated_at": { "$date": { "$numberLong": "1577774308193" } } } ], "created_at": "2019-12-31 14:37:59", "updated_at": "2019-12-31 14:38:28", "__v": 1 } ]

any suggestion return format _id to string, and date format to string. thanks

@sascha-pioneer
Copy link

I've got the same issue. It seems that embedded Relationships are not getting serialized when calling the toArray() method on the parent model (which is called automatically by Laravel to prepare the data for the response).

Therefore no mutations specified in the model class (e.g. $dates, $casts etc.) being executed and the _id value remains an instance of the MongoDB\BSON\ObjectId.

To solve the problem i've extended the Jenssegers\Mongodb\Eloquent\Model class to be able to override the attributesToArray method:

class MyModel extends Jenssegers\Mongodb\Eloquent\Model
{
    /**
     * Added serialization for embedded documents.
     *
     * @inheritDoc
     */
    public function attributesToArray()
    {
        $attributes = parent::attributesToArray();

        foreach($attributes as $key => $value) {
            if($this->isEmbedsRelationship($key)) {
                $attributes[$key] = $this->serializeEmbedded($key);
            }
        }

        return $attributes;
    }

    /**
     * Determine if the given key belongs to an embeds one or an embeds many relationship.
     *
     * @param string $key
     *
     * @return bool
     */
    public function isEmbedsRelationship(string $key): bool
    {
        return method_exists($this, $key) && $this->getAttribute($key) instanceof EmbedsOneOrMany;
    }

    /**
     * Serialize all embedded models to apply all mutations defined in the model class and
     * to serialize the MongoId to string.
     *
     * @param string $key
     *
     * @return mixed
     */
    protected function serializeEmbedded(string $key)
    {
        $data = $this->getAttribute($key);
        $model = $this->$key()->getRelated();

        if($data instanceof EmbedsMany) {
            return $model::hydrate($data->toArray());
        }

        $attribute = $this->getAttribute($key);

        return ((new $model)->newInstance($attribute, true))->toArray();
    }
}

To make this work you have to create a model class for each embedded document!

class User extends MyModel 
{
    public function address()
    {
        return $this->embedsOne(Address::class);
    }

    public function addresses()
    {
        return $this->embedsMany(Address::class);
    }    
}

class Address extends MyModel 
{
    /**
     * The attributes that should be mutated to dates.
     *
     * @var array
     */
    protected $dates = ['created_at'];

    /**
     * The attributes that should be casted to native types.
     *
     * @var array
     */
    protected $casts = ['created_at' => 'datetime:d.m.Y'];
}

When requesting a user all addresses are now serialized as expected.

@divine
Copy link
Contributor

divine commented Feb 26, 2020

Hi guys,

PRs are welcome, however keep in mind that support for embeds relationship will be dropped in the next version 4 release. The reason is simple, it doesn't work as people expect and has bugs which is nobody fixes.

Version 3.6 will continue to receive fixes only.

Thanks.

@mattg66
Copy link

mattg66 commented Apr 6, 2021

Any updates on when we can expect version 4? Wondering whether to use embedded documents or not on a new project now.

@divine
Copy link
Contributor

divine commented Apr 6, 2021

Any updates on when we can expect version 4? Wondering whether to use embedded documents or not on a new project now.

Hello @mattg66,

You can use embedded documents without the "eloquent" magic that this library provides. Embedded documents are fine, however, the "magic" is what is causing issues.

Regarding the new version don't expect it soon. Nobody likes breaking changes and it has been explained #1974 that there things that need to fixed upstream which is never going to happen - you might know the reason already.

Thanks!

@danieldavies99
Copy link

danieldavies99 commented Apr 22, 2022

I was able to work around this issue by formatting the dates at a resource level:

class FooResource extends JsonResource
{
    protected function formatMetaData($metaData) {
        if($metaData["dates"]["startDateTime"]) {
            $metaData["dates"]["startDateTime"] = (new Carbon($metaData["dates"]["startDateTime"]->toDateTime()))->toISOString();
        }
        if($metaData["dates"]["endDateTime"]) {
            $metaData["dates"]["endDateTime"] = (new Carbon($metaData["dates"]["endDateTime"]->toDateTime()))->toISOString();
        }
        return $metaData;
    }
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'name' => $this->offerName,
            'status' => $this->offerStatus,
            'metaData' => $this->formatQualifiers($this->metaData),
        ];
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants