azure-functions-java-worker icon indicating copy to clipboard operation
azure-functions-java-worker copied to clipboard

Inconsistent behavior between OutputBinding<String> and OutputBinding<List<String>>

Open JuntaoZhu opened this issue 6 years ago • 1 comments

The following code results in 2 rows in the cosmos DB

    @FunctionName("CosmosDBTriggerJava1")
    public void run(
        @CosmosDBTrigger(
            name = "items",
            databaseName = "db1",
            collectionName = "col3",
            leaseCollectionName="leases",
            connectionStringSetting = "juzhucos_DOCUMENTDB",
            createLeaseCollectionIfNotExists = true
        )
        Object[] items,
        @CosmosDBOutput(name = "database",
        databaseName = "db1",
        collectionName = "col2",
        connectionStringSetting = "juzhucos_DOCUMENTDB")
        OutputBinding<String> outputItem, //this line caused error. It works with OutputBinding<List<Object>> and OutputBinding<String>
        final ExecutionContext context
    ) {
        outputItem.setValue("[{\"name\":\"value 0\"},{\"name\":\"value 1\"}]");
    }

While the following code results in error

    @FunctionName("CosmosDBTriggerJava")
    public void run(
        @CosmosDBTrigger(
            name = "items",
            databaseName = "db1",
            collectionName = "col1",
            leaseCollectionName="leases",
            connectionStringSetting = "juzhucos_DOCUMENTDB",
            createLeaseCollectionIfNotExists = true
        )
        Object[] input,
        @CosmosDBOutput(name = "database",
        databaseName = "db1",
        collectionName = "col2",
        connectionStringSetting = "juzhucos_DOCUMENTDB")
        OutputBinding<List<String>> outputItem,
        final ExecutionContext context
    ) {
        List<String> items = new ArrayList<String>();

        for (int i = 0; i < 2; i ++) {
          items.add("{\"name\":\"value 0\"}");
        }

        outputItem.setValue(items);
    }

[1/19/2020 8:47:13 AM] Executed 'Functions.CosmosDBTriggerJava' (Failed, Id=8e0edee5-49f5-4afc-82ff-fde57a43b67b)
[1/19/2020 8:47:13 AM] System.Private.CoreLib: Exception while executing function: Functions.CosmosDBTriggerJava. Microsoft.Azure.DocumentDB.Core: Value cannot be null.
[1/19/2020 8:47:13 AM] Parameter name: document.

This is because the first one is serialized to an array of 2 JSON objects, when doing RPC from the Java worker to the function host.

[{"name":"value 0"},{"name":"value 1"}]

While the later one is serialized to an array of 2 strings

["{\"name\":\"value 0\"}","{\"name\":\"value 1\"}"]

Can we make the later one support adding JSON string objects to the list, since the first one supports that.

JuntaoZhu avatar Jan 19 '20 08:01 JuntaoZhu

Thanks for reporting this @JuntaoZhu I had exactly the problem and it was hard to debug that in Azure functions. For all others, here's a complete "Input/Output" example for multiple documents (it even works with List<Map<String, Object>>):

    @FunctionName("MyFunction")
    @CosmosDBOutput(
            name = "cosmosDbOutput",
            databaseName = "myDatabase",
            collectionName = "container1",
            connectionStringSetting = "cosmosDbConnection"
    )
    public List<Map<String, Object>> run(
            @CosmosDBTrigger(
                    name = "cosmosDbTrigger",
                    databaseName = "myDatabase",
                    collectionName = "container2",
                    leaseCollectionName = "leases",
                    createLeaseCollectionIfNotExists = true,
                    connectionStringSetting =  "cosmosDbConnection"
            ) List<String> itemStrings,
            ExecutionContext context
    ) {
        context.getLogger().log(INFO, () -> "Received " + itemStrings.size() + " items");
        List<Map<String, Object>> items = itemStrings.stream()
                .map(item -> deserializeItem(context, item))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        Set<String> itemKeys = items.stream().flatMap(item -> item.keySet().stream()).collect(Collectors.toSet());
        context.getLogger().log(INFO, () -> "Discovered item keys " + itemKeys);
        return items;
    }

    private Map<String, Object> deserializeItem(ExecutionContext context, String item) {
        try {
            return OBJECT_MAPPER.readValue(item, new TypeReference<Map<String, Object>>() {
            });
        } catch (JsonProcessingException e) {
            context.getLogger().log(WARNING, () -> "Cannot deserialize '" + item + "': " + ExceptionUtils.getStackTrace(e));
            return null;
        }
    }

neiser avatar Mar 17 '21 13:03 neiser