Skip to content

[4.x]: Custom fields have double-encoded values if the content column type is Schema::TYPE_JSON #13916

@khalwat

Description

@khalwat

What happened?

Description

I have a custom Field that adds a Schema::TYPE_JSON content column type. Upon saving the field, the values in the field are double-encoded.

Craft does this in the saveContent() method in Content service: https://github.com/craftcms/cms/blob/develop/src/services/Content.php#L79

        foreach ($serializedFieldValues as $fieldUid => $value) {
            $field = $fields[$fieldUid];
            $type = $field->getContentColumnType();

            if (is_array($type)) {
                foreach (array_keys($type) as $i => $key) {
                    $column = ElementHelper::fieldColumnFromField($field, $i !== 0 ? $key : null);
                    $values[$column] = Db::prepareValueForDb($value[$key] ?? null);
                }
            } else {
                $column = ElementHelper::fieldColumnFromField($field);
                $values[$column] = Db::prepareValueForDb($value);
            }
        }

...and then in Db::prepareValueForDb() it does this: https://github.com/craftcms/cms/blob/develop/src/helpers/Db.php#L128

        // If this isn’t a JSON column and the value is an object or array, JSON-encode it
        if (
            !in_array($columnType, [Schema::TYPE_JSON, YiiPgqslSchema::TYPE_JSONB]) &&
            (is_object($value) || is_array($value))
        ) {
            return Json::encode($value);
        }

The problem is it isn't passing in the $columnType when calling Db::prepareValueForDb() so it always encodes the value before passing it to Yii2... which then also encodes the value because it's a JSON column.

So you end up with stored values that are double-encoded as JSON.

Here's the Field in question: https://github.com/lsst-epo/canto-dam-assets/blob/develop-v4/src/fields/CantoDamAsset.php

Steps to reproduce

  1. Have a custom Field that has a content column type of Schema::TYPE_JSON
  2. Save an object or array into that field's value

Expected behavior

The value will only be encoded once as JSON

Actual behavior

The value is actually doubly encoded as JSON

Craft CMS version

4.5.5

PHP version

n/a

Operating system and version

n/a

Database type and version

MySQL 8.0.32

Image driver and version

n/a

Installed plugins and versions

n/a

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions