JSON serializer error with BotFramework an LUIS

天涯浪子 提交于 2019-12-31 03:53:08

问题


StackOverflow Community!

I have a chatbot, and integrated LUIS.ai to make it smarter. One of the dialogues is about to Book an appointment with a Supervisor (Teacher) Everything has been working fine, with literally the same code. A couple of hours ago I am experiencing some strange errors.

 Exception: Type 'Newtonsoft.Json.Linq.JArray' in Assembly 'Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' is not marked as serializable.

How to reproduce the error?

If the both Entity (Teacher and Date) is missing from the user's input it works FINE, the bot builds the Form, asking for missing inputs and presents the suggested meeting times.

If one of the Entity is missing it from input it will build a Form and asks for the missing Date or Teacher Entity and presents the suggested meeting times.

BUT

If the user's input contains both Entity: the Teacher and the Date too, then I am getting the error.

Here is my WebApiConfig class:

public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Json settings
            config.Formatters.JsonFormatter.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
            config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            config.Formatters.JsonFormatter.SerializerSettings.Formatting = Formatting.Indented;
            JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
            {
                ContractResolver = new CamelCasePropertyNamesContractResolver(),
                Formatting = Newtonsoft.Json.Formatting.Indented,
                NullValueHandling = NullValueHandling.Ignore,
            };

            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }

I am only experiencing this error when I am trying to get an Entity from the user utterance, which is a type of builtin.dateTimeV2.

This async method is called:

    //From the LUIS.AI language model the entities
    private const string EntityMeetingDate = "MeetingDate";
    private const string EntityTeacher = "Teacher";

    [LuisIntent("BookSupervision")]
public async Task BookAppointment(IDialogContext context, IAwaitable<IMessageActivity> activity, LuisResult result)
{
    var message = await activity;
    await context.PostAsync($"I am analysing your message: '{message.Text}'...");

    var meetingsQuery = new MeetingsQuery();

    EntityRecommendation teacherEntityRecommendation;
    EntityRecommendation dateEntityRecommendation;

    if (result.TryFindEntity(EntityTeacher, out teacherEntityRecommendation))
    {
        teacherEntityRecommendation.Type = "Name";
    }
    if (result.TryFindEntity(EntityMeetingDate, out dateEntityRecommendation))
    {
        dateEntityRecommendation.Type = "Date";
    }

    var meetingsFormDialog = new FormDialog<MeetingsQuery>(meetingsQuery, this.BuildMeetingsForm, FormOptions.PromptInStart, result.Entities);
    context.Call(meetingsFormDialog, this.ResumeAfterMeetingsFormDialog);

}

Further methods for building a form:

 private IForm<MeetingsQuery> BuildMeetingsForm()
{
    OnCompletionAsyncDelegate<MeetingsQuery> processMeetingsSearch = async (context, state) =>
    {
        var message = "Searching for supervision slots";
        if (!string.IsNullOrEmpty(state.Date))
        {
            message += $" at {state.Date}...";
        }
        else if (!string.IsNullOrEmpty(state.Name))
        {
            message += $" with professor {state.Name}...";
        }
        await context.PostAsync(message);
    };

    return new FormBuilder<MeetingsQuery>()
        .Field(nameof(MeetingsQuery.Date), (state) => string.IsNullOrEmpty(state.Date))
        .Field(nameof(MeetingsQuery.Name), (state) => string.IsNullOrEmpty(state.Name))
        .OnCompletion(processMeetingsSearch)
        .Build();
}

private async Task ResumeAfterMeetingsFormDialog(IDialogContext context, IAwaitable<MeetingsQuery> result)
{
try
{
    var searchQuery = await result;

    var meetings = await this.GetMeetingsAsync(searchQuery);

    await context.PostAsync($"I found {meetings.Count()} available slots:");

    var resultMessage = context.MakeMessage();
    resultMessage.AttachmentLayout = AttachmentLayoutTypes.Carousel;
    resultMessage.Attachments = new List<Attachment>();

    foreach (var meeting in meetings)
    {
        HeroCard heroCard = new HeroCard()
        {
            Title = meeting.Teacher,
            Subtitle = meeting.Location,
            Text = meeting.DateTime,
            Images = new List<CardImage>()
            {
                new CardImage() {Url = meeting.Image}
            },
            Buttons = new List<CardAction>()
            {
                new CardAction()
                {
                    Title = "Book Appointment",
                    Type = ActionTypes.OpenUrl,
                    Value = $"https://www.bing.com/search?q=easj+roskilde+" + HttpUtility.UrlEncode(meeting.Location)
                }
            }
        };

        resultMessage.Attachments.Add(heroCard.ToAttachment());
    }

    await context.PostAsync(resultMessage);
}
catch (FormCanceledException ex)
{
    string reply;

    if (ex.InnerException == null)
    {
        reply = "You have canceled the operation.";
    }
    else
    {
        reply = $"Oops! Something went wrong :( Technical Details: {ex.InnerException.Message}";
    }

    await context.PostAsync(reply);
}
finally
{
    context.Wait(DeconstructionOfDialog);
}
}


private async Task<IEnumerable<Meeting>> GetMeetingsAsync(MeetingsQuery searchQuery)
{
    var meetings = new List<Meeting>();

    //some random result manually for demo purposes
    for (int i = 1; i <= 5; i++)
    {
        var random = new Random(i);
        Meeting meeting = new Meeting()
        {
            DateTime = $" Available time: {searchQuery.Date} At building {i}",
            Teacher = $" Professor {searchQuery.Name}",
            Location = $" Elisagårdsvej 3, Room {random.Next(1, 300)}",
            Image = $"https://placeholdit.imgix.net/~text?txtsize=35&txt=Supervision+{i}&w=500&h=260"
        };

        meetings.Add(meeting);
    }

    return meetings;
}

The strangest thing that this code has worked, and my shoutout and respect goes for the community on GitHub, because I think this is an awesome platform with a tons of documentation and samples.


回答1:


This is known issue (also reported here and here).

Long story short, since the builtin.datetimeV2.* entities are not yet supported in BotBuilder, the Resolution dictionary of the EntityRecommendation ends up with an entry with a value of type JArray. The problem arises when you pass those entities to a FormDialog. Since the entities are a private field in the dialog and of course, as any other dialog, is being serialized, an exception is being thrown because the JArray class from Newtonsoft is not marked as serializable.

The request for adding support for datetimeV2 entities is here.

The workaround I can think of right now is to extract the value of the DateTime entity manually and assign it your Date field of your MeetingsQuery instance you are passing to the FormDialog and also to remove the DateTime entity from the result.Entities collection that you are passing to the FormDialog.

Update

This is already fixed in the SDK as you can see in this Pull Request.



来源:https://stackoverflow.com/questions/44552030/json-serializer-error-with-botframework-an-luis

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!