Open karamem0 opened 1 day ago
@karamem0 Thanks! Would it be possible to share the code for a repro? This was one of the trickier areas to switch from NewtonSoft to System.Text.Json.
@karamem0 I have a dialog like this. Same as yours?
public class TestDialog : ComponentDialog
{
public TestDialog(UserState userState)
: base(nameof(TestDialog))
{
// This array defines how the Waterfall will execute.
var waterfallSteps = new WaterfallStep[]
{
StepOne,
StepTwo,
StepThree
};
// Add named dialogs to the DialogSet. These names are saved in the dialog state.
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new TextPrompt(nameof(TextPrompt)));
// The initial child Dialog to run.
InitialDialogId = nameof(WaterfallDialog);
}
private static async Task<DialogTurnResult> StepOne(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values["Value1"] = new Dictionary<string, object?>() { { "Key1", "Value1" } };
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = MessageFactory.Text("One") }, cancellationToken);
}
private static async Task<DialogTurnResult> StepTwo(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values["Value2"] = "This is a bot";
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = MessageFactory.Text("Two") }, cancellationToken);
}
private static async Task<DialogTurnResult> StepThree(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = MessageFactory.Text($"Three: {stepContext.Result}") }, cancellationToken);
}
}
Hi @tracyboehrer, Yes, that's right. The actual code is more complex, but I'm trying to do something similar.
As a workaround, I created the extension method.
public static class WaterfallStepContextExtensions
{
public static void SetValue<T>(this WaterfallStepContext target, string key, T? value)
{
target.Values[key] = JsonSerializer.Serialize(value);
}
public static T? GetValue<T>(this WaterfallStepContext target, string key)
{
if (target.Values.TryGetValue(key, out var value))
{
if (value is string jsonStr)
{
return JsonSerializer.Deserialize<T>(jsonStr);
}
else
if (value is JsonElement element)
{
var jsonObj = element.GetString();
if (jsonObj is not null)
{
return JsonSerializer.Deserialize<T>(jsonObj);
}
}
}
return default;
}
}
@karamem0 My version works with MemoryStorage, which if we're doing the same thing the problem isn't where I thought it would be. Possibly CosmosDbPartitionedStorage? I will check that next. My initial guess was something up the chain... ObjectPath. Because whatever it's doing, the serializer doesn't like it. In all likelihood, this is just a difference between System.Text.Json and NewtonSoft, and we didn't account for it.
@karamem0 So not storage related which makes sense because that isn't in the stack. Though, I can't reproduce. Cleary it's happening for you.
@tracyboehrer
I changed your code a little then I could reproduce the issue. I tested with BlobsStorage and Azurite.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.BotBuilder;
using Microsoft.Agents.BotBuilder.Dialogs;
using Microsoft.Agents.Protocols.Primitives;
namespace EchoBot.Dialogs
{
public class TestDialog : ComponentDialog
{
public TestDialog(UserState userState)
: base(nameof(TestDialog))
{
// This array defines how the Waterfall will execute.
var waterfallSteps = new WaterfallStep[]
{
StepOne,
StepTwo,
StepThree
};
// Add named dialogs to the DialogSet. These names are saved in the dialog state.
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new TextPrompt(nameof(TextPrompt)));
// The initial child Dialog to run.
InitialDialogId = nameof(WaterfallDialog);
}
private static async Task<DialogTurnResult> StepOne(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values["Value1"] = new Dictionary<string, object?>() { { "Key1", "Value1" } };
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = MessageFactory.Text("One") }, cancellationToken);
}
private static async Task<DialogTurnResult> StepTwo(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
- stepContext.Values["Value2"] = "This is a bot";
+ stepContext.Values["Value2"] = new Dictionary<int, object?>() { { 2, "Value2" } };
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = MessageFactory.Text("Two") }, cancellationToken);
}
private static async Task<DialogTurnResult> StepThree(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = MessageFactory.Text($"Three: {stepContext.Result}") }, cancellationToken);
}
}
}
Version
What package version of the SDK are you using.
Describe the bug When inserting objects of different types into WaterfallStepContext.Values, the deserialization process fails.
To Reproduce
BlobsStorage
asIStorage
.WaterfallDialog
, .At the first step:
Add
Dictionary<string, object>
object to theStepContext.Value
.Value1
is saved to Blob storage with '$type' and '$typeAssembly')At the second step:
Add
string
object to theStepContext.Value
.Value2
is saved to Blob storage)The JSON is:
Expected behavior The values should be deserialized with its type or
JsonElement
.Screenshots N/A
Hosting Information (please complete the following information):
Additional context Add any other context about the problem here.