Home > Software design >  AdaptiveCards throws an exception during .NET parsing
AdaptiveCards throws an exception during .NET parsing

Time:09-21

I currently encountered the following problem parsing an adaptive card.

This is the card:

{
    "type": "AdaptiveCard",
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.4",
    "body": [
        {
            "type": "TextBlock",
            "text": "{{DATE(${$root.AdditionalData['DUE-DATE']},COMPACT)}}",
            "wrap": true
        }
    ]
}

This is the card-content:

{
    "AdditionalData": {
      "DUE-DATE": "2021-09-10T16:29:59Z"
    }
}

Code: c# on .NET Framework 4.7.2 where layout is a string with the above card and content is a string with the above card-content:

 AdaptiveCardTemplate template = new AdaptiveCardTemplate(layout);
 string cardJson = template.Expand(content);

 AdaptiveCardParseResult card = AdaptiveCard.FromJson(cardJson);

And it crashes with:

AdaptiveCards.AdaptiveSerializationException: 'Error reading string. Unexpected token: Undefined. Path 'text', line 1, position 137.'
JsonReaderException: Error reading string. Unexpected token: Undefined. Path 'text', line 1, position 137.

The generated JSON on cardJson looks wrong to me at the text property:

{"type":"AdaptiveCard","$schema":"http://adaptivecards.io/schemas/adaptive-card.json","version":"1.4","body":[{"type":"TextBlock","text":,"wrap":true}]}

I'm using the adaptive cards nuget packages:

  • AdaptiveCards 2.7.2
  • AdaptiveCards.Templating 1.2.

Did I encounter a parsing bug? The value for the text property should be 10.9.2021.

In the designer on adaptivecards.io everything works fine for some reason. Does anyone have a fix/workaround?

CodePudding user response:

If you want the literal "text":"10.9.2021" to appear in your cardJson, use "${formatDateTime(AdditionalData['DUE-DATE'], 'd.M.yyyy')}" to generate the required value for your TextBlock:

{
  "type": "AdaptiveCard",
  "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "version": "1.4",
  "body": [
    {
      "type": "TextBlock",
      "text": "${formatDateTime(AdditionalData['DUE-DATE'], 'd.M.yyyy')}",
      "wrap": true
    }
  ]
}

This causes all date formatting to be performed by the AdaptiveCardTemplate and results in:

{
   "type":"AdaptiveCard",
   "$schema":"http://adaptivecards.io/schemas/adaptive-card.json",
   "version":"1.4",
   "body":[
      {
         "type":"TextBlock",
         "text":"10.9.2021",
         "wrap":true
      }
   ]
}

Demo fiddle #1 here.

If you would prefer "{{DATE(2021-09-10T16:29:59Z, COMPACT)}}" in your cardJson which delegates date formatting to the TextBlock, use:

{
  "type": "AdaptiveCard",
  "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "version": "1.4",
  "body": [
    {
      "type": "TextBlock",
      "text": "{{DATE(${AdditionalData['DUE-DATE']}, COMPACT)}}",
      "wrap": true
    }
  ]
}

Which results in

      "text": "{{DATE(2021-09-10T16:29:59Z, COMPACT)}}",

Demo fiddle #2 here.

Notes:

  1. According to the Microsoft documentation:

    • Use Dot-notation to access sub-objects of an object hierarchy. E.g., ${myParent.myChild}
    • Use Indexer syntax to retrieve properties by key or items in an array. E.g., ${myArray[0]}

    But, when accessing an object property with a hyphen (or some other reserved operator) in its name, it is apparently necessary to use the Indexer syntax ['DUE-DATE'] instead of Dot‑notation to retrieve its value, passing the property name inside a single-quoted string as the indexer.

  2. According to the docs

    There are a few reserved keywords to access various binding scopes. ...

    "$root": "The root data object. Useful when iterating to escape to parent object",
    

    Thus you do not need to use $root when accessing properties of the currently scoped object (which is, by default, the root) when using Dot-notation. If for whatever reason you need or want to address the root object directly, you may use $root like so:

    "text": "${formatDateTime($root.AdditionalData['DUE-DATE'], 'd.M.yyyy')}",
    

    Demo fiddle #3 here.

    However, it seems that using $root in combination with {{DATE()}} causes malformed JSON to be generated. I.e.

      "text": "{{DATE(${$root.AdditionalData['DUE-DATE']}, COMPACT)}}",
    

    results in "text":, as indicated in your question.

    Demo fiddle #4 here.

    This looks to be a bug in the framework. Possibly the parser is choking on the sequence of tokens ${$, as your issue somewhat resembles Issue #6026: [Authoring][.NET][Templating] Inconsistency in accessing $root inside a $when property in adaptive card templating which reports a failure to parse "$when": "${$root.UserName != null}" correctly.

    You can avoid the problem either by omitting $root entirely, or by wrapping $root.AdditionalData['DUE-DATE'] in an additional formatDateTime() like so:

       "text": "{{DATE(${formatDateTime($root.AdditionalData['DUE-DATE'])}, COMPACT)}}",
    

    Resulting in

       "text": "{{DATE(2021-09-10T16:29:59.000Z, COMPACT)}}",
    

    Demo fiddle #5 here.

  3. From the documentation page Adaptive Card Templating SDKs: Troubleshooting:

    Q. Why date/time in RFC 3389 format e.g "2017-02-14T06:08:00Z" when used with template doesn't works with TIME/DATE functions?
    A. .NET sdk nuget version 1.0.0-rc.0 exhibits this behavior. this behavior is corrected in the subsequent releases... Please use formatDateTime() function to format the date/time string to RFC 3389 as seen in this example, or you can bypass TIME/DATE functions, and just use formatDateTime(). For more information on formatDateTime(), please go here.

    While this recommendation to use formatDateTime was to fix a problem in 1.0.0-rc.0, the trick also resolves the issue mentioned in note #2 above.

  • Related