Repository: localstack/localstack

Issue:
DynamoDb PutItem - Object of type bytes is not JSON serializable Traceback
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

I am using `Docker version 20.10.16, build aa7e414` and the latest localstack [0.14.3](https://hub.docker.com/layers/localstack/localstack/localstack/0.14.3/images/sha256-c47f5ede1e5fc4fec941ef8605e0b85be66f8b61ed20828e2ea1e51581637932?context=explore).

When I am calling DynamoDB PutItem, it yields this error:

```
2022-05-25T01:57:42.357:WARNING:localstack.utils.server.http2_server: Error in proxy handler for request POST http://localhost:4566/: Object of type bytes is not JSON serializable Traceback (most recent call last):
localstack    |   File "/opt/code/localstack/localstack/utils/server/http2_server.py", line 185, in index
localstack    |     result = await run_sync(handler, request, data)
localstack    |   File "/opt/code/localstack/localstack/utils/asyncio.py", line 85, in run_sync
localstack    |     return await loop.run_in_executor(thread_pool, copy_context().run, func_wrapped)
localstack    |   File "/opt/code/localstack/localstack/utils/threads.py", line 39, in run
localstack    |     result = self.func(self.params, **kwargs)
localstack    |   File "/opt/code/localstack/localstack/utils/asyncio.py", line 30, in _run
localstack    |     return fn(*args, **kwargs)
localstack    |   File "/opt/code/localstack/localstack/services/generic_proxy.py", line 992, in handler
localstack    |     return modify_and_forward(
localstack    |   File "/opt/code/localstack/localstack/services/generic_proxy.py", line 534, in wrapper
localstack    |     value = func(*args, **kwargs)
localstack    |   File "/opt/code/localstack/localstack/services/generic_proxy.py", line 614, in modify_and_forward
localstack    |     listener_result = listener.forward_request(
localstack    |   File "/opt/code/localstack/localstack/services/edge.py", line 170, in forward_request
localstack    |     result = do_forward_request(api, method, path, data, headers, port=port)
localstack    |   File "/opt/code/localstack/localstack/services/edge.py", line 225, in do_forward_request
localstack    |     result = do_forward_request_inmem(api, method, path, data, headers, port=port)
localstack    |   File "/opt/code/localstack/localstack/services/edge.py", line 249, in do_forward_request_inmem
localstack    |     response = modify_and_forward(
localstack    |   File "/opt/code/localstack/localstack/services/generic_proxy.py", line 534, in wrapper
localstack    |     value = func(*args, **kwargs)
localstack    |   File "/opt/code/localstack/localstack/services/generic_proxy.py", line 614, in modify_and_forward
localstack    |     listener_result = listener.forward_request(
localstack    |   File "/opt/code/localstack/localstack/http/adapters.py", line 51, in forward_request
localstack    |     response = self.request(request)
localstack    |   File "/opt/code/localstack/localstack/aws/proxy.py", line 42, in request
localstack    |     return self.skeleton.invoke(context)
localstack    |   File "/opt/code/localstack/localstack/aws/skeleton.py", line 153, in invoke
localstack    |     return self.dispatch_request(context, instance)
localstack    |   File "/opt/code/localstack/localstack/aws/skeleton.py", line 165, in dispatch_request
localstack    |     result = handler(context, instance) or {}
localstack    |   File "/opt/code/localstack/localstack/aws/forwarder.py", line 57, in _call
localstack    |     return handler(context, req)
localstack    |   File "/opt/code/localstack/localstack/aws/skeleton.py", line 117, in __call__
localstack    |     return self.fn(*args, **kwargs)
localstack    |   File "/opt/code/localstack/localstack/aws/api/core.py", line 95, in operation_marker
localstack    |     return fn(*args, **kwargs)
localstack    |   File "/opt/code/localstack/localstack/services/dynamodb/provider.py", line 752, in transact_write_items
localstack    |     records = self.prepare_transact_write_item_records(
localstack    |   File "/opt/code/localstack/localstack/services/dynamodb/provider.py", line 1094, in prepare_transact_write_item_records
localstack    |     new_record["dynamodb"]["SizeBytes"] = len(json.dumps(put_request["Item"]))
localstack    |   File "/usr/local/lib/python3.10/json/__init__.py", line 231, in dumps
localstack    |     return _default_encoder.encode(obj)
localstack    |   File "/usr/local/lib/python3.10/json/encoder.py", line 199, in encode
localstack    |     chunks = self.iterencode(o, _one_shot=True)
localstack    |   File "/usr/local/lib/python3.10/json/encoder.py", line 257, in iterencode
localstack    |     return _iterencode(o, 0)
localstack    |   File "/usr/local/lib/python3.10/json/encoder.py", line 179, in default
localstack    |     raise TypeError(f'Object of type {o.__class__.__name__} '
localstack    | TypeError: Object of type bytes is not JSON serializable
```


### Expected Behavior

_No response_

### How are you starting LocalStack?

With a docker-compose file

### Steps To Reproduce

#### How are you starting localstack (e.g., `bin/localstack` command, arguments, or `docker-compose.yml`)

```
version: "2.1"

services:
  localstack:
    image: localstack/localstack
    container_name: localstack
    ports:
      - "4566-4599:4566-4599"
      - "9000:9000"
    volumes:
      - localstack:/tmp/localstack/data
      - /dev/null:/tmp/localstack/data/recorded_api_calls.json
    environment:
      - SERVICES=sqs,dynamodb,s3
      - AWS_DEFAULT_REGION=us-east-1
      - DATA_DIR=/tmp/localstack/data
      - PORT_WEB_UI=9000
      - LAMBDA_EXECUTOR=local
      - EDGE_PORT=4566
      - DOCKER_HOST=unix:///var/run/docker.sock
      - START_WEB=1
        
volumes:
  localstack:
```


#### Client commands (e.g., AWS SDK code snippet, or sequence of "awslocal" commands)

   i am using golang aws sdk (but it was working and I didn't update anything here)


### Environment

```markdown
- OS: Ubuntu
- LocalStack: 0.14.3
```


### Anything else?

_No response_


Discussion:
Welcome to LocalStack! Thanks for reporting your first issue and our team will be working towards fixing the issue for you or reach out for more background information. We recommend joining our [Slack Community](https://localstack.cloud/contact/) for real-time help and drop a message to LocalStack Pro Support if you are a Pro user! If you are willing to contribute towards fixing this issue, please have a look at our [contributing guidelines](https://github.com/localstack/.github/blob/main/CONTRIBUTING.md) and our [developer guide](https://docs.localstack.cloud/developer-guide/).
hi @arnaldobadin , thanks for reporting. could you please share the CreateTable and the concrete PutItem command you are running so we can try to reproduce it?
Same problem, a regression on 0.14.3 - worked OK on 0.14.2 

Narrowed down to a transactional write of an item with a byte array, e.g. (Kotlin): 

````kotlin
        amazonDynamoDb.transactWriteItems(
            TransactWriteItemsRequest().withTransactItems(
                TransactWriteItem().withPut(
                    Put().withTableName(tableName)
                        .withItem(
                            mapOf(
                                "id" to AttributeValue(UUID.randomUUID().toString()),
                                "byteArray" to AttributeValue().withB(ByteBuffer.wrap("abcd".toByteArray())),
                            )
                        )
                )
            )
        )

````

Resolving patch:
diff --git a/localstack/services/dynamodb/provider.py b/localstack/services/dynamodb/provider.py
--- a/localstack/services/dynamodb/provider.py
+++ b/localstack/services/dynamodb/provider.py
@@ -1091,7 +1091,7 @@ def prepare_transact_write_item_records(self, transact_items, existing_items):
                 if existing_item:
                     new_record["dynamodb"]["OldImage"] = existing_item
                 new_record["eventSourceARN"] = aws_stack.dynamodb_table_arn(table_name)
-                new_record["dynamodb"]["SizeBytes"] = len(json.dumps(put_request["Item"]))
+                new_record["dynamodb"]["SizeBytes"] = _get_size_bytes(put_request["Item"])
                 records.append(new_record)
                 i += 1
             update_request = request.get("Update")
@@ -1111,7 +1111,7 @@ def prepare_transact_write_item_records(self, transact_items, existing_items):
                 new_record["dynamodb"]["OldImage"] = existing_items[i]
                 new_record["dynamodb"]["NewImage"] = updated_item
                 new_record["eventSourceARN"] = aws_stack.dynamodb_table_arn(table_name)
-                new_record["dynamodb"]["SizeBytes"] = len(json.dumps(updated_item))
+                new_record["dynamodb"]["SizeBytes"] = _get_size_bytes(updated_item)
                 records.append(new_record)
                 i += 1
             delete_request = request.get("Delete")
@@ -1127,7 +1127,7 @@ def prepare_transact_write_item_records(self, transact_items, existing_items):
                 new_record["eventName"] = "REMOVE"
                 new_record["dynamodb"]["Keys"] = keys
                 new_record["dynamodb"]["OldImage"] = existing_item
-                new_record["dynamodb"]["SizeBytes"] = len(json.dumps(existing_item))
+                new_record["dynamodb"]["SizeBytes"] = _get_size_bytes(existing_items)
                 new_record["eventSourceARN"] = aws_stack.dynamodb_table_arn(table_name)
                 records.append(new_record)
                 i += 1


Test patch:
diff --git a/tests/integration/test_dynamodb.py b/tests/integration/test_dynamodb.py
--- a/tests/integration/test_dynamodb.py
+++ b/tests/integration/test_dynamodb.py
@@ -948,6 +948,38 @@ def test_transaction_write_items(self):
         # clean up
         dynamodb.delete_table(TableName=table_name)
 
+    def test_transaction_write_binary_data(self):
+        table_name = "test-ddb-table-%s" % short_uid()
+        dynamodb = aws_stack.create_external_boto_client("dynamodb")
+        dynamodb.create_table(
+            TableName=table_name,
+            KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
+            AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
+            ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
+            Tags=TEST_DDB_TAGS,
+        )
+        binary_item = {"B": b"foobar"}
+        response = dynamodb.transact_write_items(
+            TransactItems=[
+                {
+                    "Put": {
+                        "TableName": table_name,
+                        "Item": {
+                            "id": {"S": "someUser"},
+                            "binaryData": binary_item,
+                        },
+                    }
+                }
+            ]
+        )
+        item = dynamodb.get_item(TableName=table_name, Key={"id": {"S": "someUser"}})["Item"]
+        assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
+        assert item["binaryData"]
+        assert item["binaryData"] == binary_item
+
+        # clean up
+        dynamodb.delete_table(TableName=table_name)
+
     def test_transact_get_items(self, dynamodb_client):
         table_name = "test-ddb-table-%s" % short_uid()
         dynamodb_client.create_table(