Handle user-usage of $id key in JSON objects
This commit is contained in:
parent
c10e1b4232
commit
14d7ec18d6
|
|
@ -58,7 +58,7 @@ public class PolymorphicObjectConverter : JsonConverter<object>
|
|||
|
||||
if (isNewtonsoftObject)
|
||||
{
|
||||
var parsedModel = JsonElement.ParseValue(ref reader)!;
|
||||
var parsedModel = JsonElement.ParseValue(ref reader);
|
||||
var newtonsoftJson = parsedModel.GetProperty(IslandPropertyName).GetString();
|
||||
return !string.IsNullOrWhiteSpace(newtonsoftJson) ? JObject.Parse(newtonsoftJson) : new JObject();
|
||||
}
|
||||
|
|
@ -68,7 +68,7 @@ public class PolymorphicObjectConverter : JsonConverter<object>
|
|||
|
||||
if (isNewtonsoftArray)
|
||||
{
|
||||
var parsedModel = JsonElement.ParseValue(ref reader)!;
|
||||
var parsedModel = JsonElement.ParseValue(ref reader);
|
||||
var newtonsoftJson = parsedModel.GetProperty(IslandPropertyName).GetString();
|
||||
return !string.IsNullOrWhiteSpace(newtonsoftJson) ? JArray.Parse(newtonsoftJson) : new JArray();
|
||||
}
|
||||
|
|
@ -78,7 +78,7 @@ public class PolymorphicObjectConverter : JsonConverter<object>
|
|||
|
||||
if (isJsonObject)
|
||||
{
|
||||
var parsedModel = JsonElement.ParseValue(ref reader)!;
|
||||
var parsedModel = JsonElement.ParseValue(ref reader);
|
||||
var systemTextJson = parsedModel.GetProperty(IslandPropertyName).GetString();
|
||||
return !string.IsNullOrWhiteSpace(systemTextJson) ? JsonObject.Parse(systemTextJson) : new JsonObject();
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ public class PolymorphicObjectConverter : JsonConverter<object>
|
|||
|
||||
if (isJsonArray)
|
||||
{
|
||||
var parsedModel = JsonElement.ParseValue(ref reader)!;
|
||||
var parsedModel = JsonElement.ParseValue(ref reader);
|
||||
var systemTextJson = parsedModel.GetProperty(IslandPropertyName).GetString();
|
||||
return !string.IsNullOrWhiteSpace(systemTextJson) ? JsonArray.Parse(systemTextJson) : new JsonArray();
|
||||
}
|
||||
|
|
@ -327,7 +327,7 @@ public class PolymorphicObjectConverter : JsonConverter<object>
|
|||
}
|
||||
case JsonTokenType.StartObject:
|
||||
var dict = new ExpandoObject() as IDictionary<string, object>;
|
||||
var referenceResolver = (options.ReferenceHandler as CrossScopedReferenceHandler)?.GetResolver();
|
||||
var referenceResolver = (CustomPreserveReferenceResolver)(options.ReferenceHandler as CrossScopedReferenceHandler)?.GetResolver()!;
|
||||
while (reader.Read())
|
||||
{
|
||||
switch (reader.TokenType)
|
||||
|
|
@ -344,13 +344,15 @@ public class PolymorphicObjectConverter : JsonConverter<object>
|
|||
if (key == RefPropertyName)
|
||||
{
|
||||
var referenceId = reader.GetString();
|
||||
var reference = referenceResolver!.ResolveReference(referenceId!);
|
||||
var reference = referenceResolver.ResolveReference(referenceId!);
|
||||
dict.Add(key, reference);
|
||||
}
|
||||
else if (key == IdPropertyName)
|
||||
{
|
||||
var referenceId = reader.GetString()!;
|
||||
referenceResolver!.AddReference(referenceId, dict);
|
||||
|
||||
// Attempt to add the reference; if not found, we can ignore it and assume that the user is using the $id property for something else, such as in JSON $schema.
|
||||
referenceResolver.TryAddReference(referenceId, dict);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -19,6 +19,14 @@ public class CustomPreserveReferenceResolver : ReferenceResolver
|
|||
if (!_referenceIdToObjectMap.TryAdd(referenceId, value))
|
||||
throw new JsonException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add a reference.
|
||||
/// </summary>
|
||||
public bool TryAddReference(string referenceId, object value)
|
||||
{
|
||||
return _referenceIdToObjectMap.TryAdd(referenceId, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetReference(object value, out bool alreadyExists)
|
||||
|
|
|
|||
|
|
@ -150,6 +150,9 @@
|
|||
<None Update="Scenarios\ParentChildOutputMapping\Workflows\consumer.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Scenarios\JsonObjectSerialization\Workflows\instance-serialization.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Expressions.Contracts;
|
||||
using Elsa.Testing.Shared;
|
||||
using Elsa.Workflows.Core;
|
||||
using Elsa.Workflows.Core.Contracts;
|
||||
using Elsa.Workflows.Core.Serialization.Converters;
|
||||
using Elsa.Workflows.Core.Serialization.ReferenceHandlers;
|
||||
using Elsa.Workflows.Core.State;
|
||||
using Elsa.Workflows.Management.Contracts;
|
||||
using Elsa.Workflows.Management.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Elsa.IntegrationTests.Scenarios.JsonObjectSerialization;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for serializing and deserializing JSON objects containing reserved keywords such as $id.
|
||||
/// </summary>
|
||||
public class Tests
|
||||
{
|
||||
private readonly CapturingTextWriter _capturingTextWriter = new();
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IWorkflowStateSerializer _workflowStateSerializer;
|
||||
|
||||
public Tests(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
_services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
|
||||
_workflowStateSerializer = _services.GetRequiredService<IWorkflowStateSerializer>();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "User-objects containing $id don't break deserialization into ExpandoObject.")]
|
||||
public async Task Test1()
|
||||
{
|
||||
// Populate registries.
|
||||
await _services.PopulateRegistriesAsync();
|
||||
|
||||
// Import workflow.
|
||||
var workflowFileName = "Scenarios/JsonObjectSerialization/Workflows/instance-serialization.json";
|
||||
var workflowDefinition = await _services.ImportWorkflowDefinitionAsync(workflowFileName);
|
||||
|
||||
// Execute.
|
||||
var workflowState = await _services.RunWorkflowUntilEndAsync(workflowDefinition.DefinitionId);
|
||||
|
||||
// Serialize workflow state.
|
||||
var serializedWorkflowState = await _workflowStateSerializer.SerializeAsync(workflowState);
|
||||
|
||||
// Attempt to deserialize workflow state.
|
||||
await _workflowStateSerializer.DeserializeAsync<WorkflowState>(serializedWorkflowState);
|
||||
|
||||
// If we reach this point, the test has passed. Otherwise, an exception would have been thrown.
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
{
|
||||
"id": "b78e443339b5bf1e",
|
||||
"definitionId": "861e51cdbe07931",
|
||||
"name": "Instance Serialization",
|
||||
"description": "This workflow stores a user-JSON object containing $id as a field, which should not affect the deserialization process form JSON back into ExpandoObject.",
|
||||
"createdAt": "2023-11-23T15:18:12.805077+00:00",
|
||||
"version": 13,
|
||||
"toolVersion": "3.0.0.0",
|
||||
"variables": [
|
||||
{
|
||||
"id": "d4f37df0c6781767",
|
||||
"name": "Variable1",
|
||||
"typeName": "Object",
|
||||
"isArray": false,
|
||||
"storageDriverTypeName": "Elsa.Workflows.Core.Services.WorkflowStorageDriver, Elsa.Workflows.Core"
|
||||
},
|
||||
{
|
||||
"id": "4e256dab27d33eac",
|
||||
"name": "Variable2",
|
||||
"typeName": "Object",
|
||||
"isArray": false,
|
||||
"storageDriverTypeName": "Elsa.Workflows.Core.Services.WorkflowStorageDriver, Elsa.Workflows.Core"
|
||||
}
|
||||
],
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"outcomes": [],
|
||||
"customProperties": {},
|
||||
"isReadonly": false,
|
||||
"isLatest": true,
|
||||
"isPublished": false,
|
||||
"options": {
|
||||
"autoUpdateConsumingWorkflows": false
|
||||
},
|
||||
"root": {
|
||||
"type": "Elsa.Flowchart",
|
||||
"version": 1,
|
||||
"id": "35cdc2a3529d9b55",
|
||||
"nodeId": "Workflow1:35cdc2a3529d9b55",
|
||||
"metadata": {},
|
||||
"customProperties": {
|
||||
"source": "FlowchartJsonConverter.cs:47",
|
||||
"notFoundConnections": [],
|
||||
"canStartWorkflow": false,
|
||||
"runAsynchronously": false
|
||||
},
|
||||
"activities": [
|
||||
{
|
||||
"variable": {
|
||||
"id": "d4f37df0c6781767",
|
||||
"name": "Variable1",
|
||||
"typeName": "Object",
|
||||
"storageDriverTypeName": "Elsa.Workflows.Core.Services.WorkflowStorageDriver, Elsa.Workflows.Core"
|
||||
},
|
||||
"value": {
|
||||
"typeName": "Object",
|
||||
"expression": {
|
||||
"type": "JavaScript",
|
||||
"value": "return {\n \u0022inputSchema\u0022: {\n \u0022$schema\u0022: \u0022http://json-schema.org/draft-07/schema#\u0022,\n \u0022title\u0022: \u0022Inform front-office afd 2.0 input schema\u0022,\n \u0022description\u0022: \u0022Schema of the AFD 2.0 message that is sent to the front-office\u0022,\n \u0022$id\u0022: \u0022#/definitions/inform-front-office-input-afd\u0022,\n \u0022type\u0022: \u0022object\u0022\n }\n}"
|
||||
},
|
||||
"memoryReference": {
|
||||
"id": "d4f32ee0ad659ba5:input-0"
|
||||
}
|
||||
},
|
||||
"id": "d4f32ee0ad659ba5",
|
||||
"nodeId": "Workflow1:35cdc2a3529d9b55:d4f32ee0ad659ba5",
|
||||
"name": "SetVariable1",
|
||||
"type": "Elsa.SetVariable",
|
||||
"version": 1,
|
||||
"customProperties": {
|
||||
"canStartWorkflow": false,
|
||||
"runAsynchronously": false
|
||||
},
|
||||
"metadata": {
|
||||
"designer": {
|
||||
"position": {
|
||||
"x": -456.5988464355469,
|
||||
"y": -104.93463134765625
|
||||
},
|
||||
"size": {
|
||||
"width": 153.828125,
|
||||
"height": 50
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"variable": {
|
||||
"id": "4e256dab27d33eac",
|
||||
"name": "Variable2",
|
||||
"typeName": "Object",
|
||||
"storageDriverTypeName": "Elsa.Workflows.Core.Services.WorkflowStorageDriver, Elsa.Workflows.Core"
|
||||
},
|
||||
"value": {
|
||||
"typeName": "Object",
|
||||
"expression": {
|
||||
"type": "JavaScript",
|
||||
"value": "return getVariable1()"
|
||||
},
|
||||
"memoryReference": {
|
||||
"id": "31c60a181a1a66d8:input-0"
|
||||
}
|
||||
},
|
||||
"id": "31c60a181a1a66d8",
|
||||
"nodeId": "Workflow1:35cdc2a3529d9b55:31c60a181a1a66d8",
|
||||
"name": "SetVariable2",
|
||||
"type": "Elsa.SetVariable",
|
||||
"version": 1,
|
||||
"customProperties": {
|
||||
"canStartWorkflow": false,
|
||||
"runAsynchronously": false
|
||||
},
|
||||
"metadata": {
|
||||
"designer": {
|
||||
"position": {
|
||||
"x": -210.69132232666016,
|
||||
"y": -104.93463134765625
|
||||
},
|
||||
"size": {
|
||||
"width": 153.828125,
|
||||
"height": 50
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"source": {
|
||||
"activity": "d4f32ee0ad659ba5",
|
||||
"port": "Done"
|
||||
},
|
||||
"target": {
|
||||
"activity": "31c60a181a1a66d8",
|
||||
"port": "In"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue