Skip to content

Add Cast for JSON #265

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

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ Please note that the following are not required, but are strongly recommended fo
## Unsupported features

- STRUCT data types
- Inserting/Updating JSON data types

## Limitations

Expand Down Expand Up @@ -235,6 +234,18 @@ When fetching rows, the library coverts the following column types

Note that if you execute a query without QueryBuilder, it will not have these conversions.

### JSON Type

In order to use JSON columns, use the provided Cast on your model as below

```php
use Colopl\Spanner\Casts\SpannerJson;

protected $casts = [
'json_column_name' => SpannerJson::class,
];
```


### Partitioned DML
You can run partitioned DML as below.
Expand Down
54 changes: 54 additions & 0 deletions src/Casts/SpannerJson.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace Colopl\Spanner\Casts;

use Google\Cloud\Spanner\PgJsonb;
use Google\Cloud\Spanner\V1\TypeAnnotationCode;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;

/**
* @implements CastsAttributes<mixed, mixed>
* */
class SpannerJson implements CastsAttributes
{
public function get(Model $model, string $key, mixed $value, array $attributes)
{
if($value === null) {
return null;
}

if($value instanceof SpannerJsonType) {
$value = (string) $value;
}

if(!is_string($value)) {
throw new \InvalidArgumentException('The given value must be a string, SpannerJsonType or null');
}

return json_decode($value, true);
}

public function set(Model $model, string $key, mixed $value, array $attributes)
{
if (
!is_array($value) &&
!$value instanceof \JsonSerializable &&
$value !== null &&
!is_string($value)
) {
throw new \InvalidArgumentException('The given value must be an array, JsonSerializable, string or null.');
}

return [$key => new SpannerJsonType($value)];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

❓ Verification inconclusive

Missing import for SpannerJsonType class.

The SpannerJsonType class is used but not imported. This will cause a runtime error.

+use Colopl\Spanner\Query\Grammar\SpannerJsonType;

 namespace Colopl\Spanner\Casts;

Please verify the correct namespace and path for the SpannerJsonType class in your codebase.


🏁 Script executed:

#!/bin/bash
# Search for the SpannerJsonType class in the codebase
echo "Searching for SpannerJsonType class..."
rg -l "class SpannerJsonType" --type php

Length of output: 129


Verify Missing Import for SpannerJsonType

The code instantiates a new SpannerJsonType($value) on line 36 of src/Casts/SpannerJson.php without an import. We attempted to verify its existence using a search for a class declaration but did not find a matching definition. Please manually verify whether the SpannerJsonType class exists in the codebase:

  • Confirm if the class is defined (possibly with a different name or in a different namespace).
  • If present, ascertain its correct namespace and update the import statement accordingly (the suggested use Colopl\Spanner\Query\Grammar\SpannerJsonType; is unconfirmed).
  • If missing entirely, consider adding the required class definition or adjusting the logic to prevent runtime errors.

Committable suggestion skipped: line range outside the PR's diff.

}
}


class SpannerJsonType extends PgJsonb
{
public function typeAnnotation(): int
{
return TypeAnnotationCode::TYPE_ANNOTATION_CODE_UNSPECIFIED;
}
}
Comment on lines +1 to +54
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Document expected behavior for numeric JSON keys.

JSON objects can have numeric keys, but PHP arrays convert these to integers which can cause unexpected behavior when encoding/decoding between PHP and JSON formats.

Please document or consider handling the case where JSON data contains numeric strings as keys, which might be transformed during the encode/decode process.


🏁 Script executed:

#!/bin/bash
# Create a sample PHP script to test numeric key behavior
cat > test_json_keys.php << 'EOF'
<?php
// Test case with numeric string keys
$jsonWithNumericKeys = '{"1": "value1", "2": "value2"}';
$decoded = json_decode($jsonWithNumericKeys, true);
echo "Original JSON: $jsonWithNumericKeys\n";
echo "Decoded array: " . print_r($decoded, true) . "\n";
$reencoded = json_encode($decoded);
echo "Re-encoded JSON: $reencoded\n";
echo "Are they identical? " . ($jsonWithNumericKeys === $reencoded ? "Yes" : "No") . "\n";
EOF

# Run the test script
php test_json_keys.php

Length of output: 206


Update Documentation on Numeric JSON Key Conversion Behavior

The test confirms that when decoding JSON with numeric string keys, PHP converts these keys to integers. When re-encoding, the keys are output as strings—which causes the re-encoded JSON not to be byte-identical to the original (e.g., extra whitespace differences aside, the numeric keys’ types have shifted). Please update the documentation to clearly explain this behavior in the context of the casting logic in SpannerJson. Consider whether the current handling is acceptable or if additional conversion logic is needed to preserve key types consistently.

  • File: src/Casts/SpannerJson.php
  • Areas to Address:
    • Document the type conversion that occurs during JSON decode/encode.
    • Explain that numeric keys are converted to integers upon decoding and back to strings upon encoding.
    • Review if any special handling is required for numeric keys in this context.