Enumeration Extension Methods Contains and distinct not working as per expectation

Hello, we observed that the Enumeration Extension Methods Contains and Distinct are not working as expected. I have attached the template, data output. Also, when using firstOrDefault or similar functions, how to set a default value? Currently, I am using the null-coalescing operator in the template syntax. Is there a recommended way?

function_query.zip (21.5 KB)

@pgcentric

Summary
The Contains, Distinct and the overload of FirstOrDefault that accepts a default value are not available in the built‑in expression language of Aspose.Words Reporting Engine. Because the engine only evaluates a limited, whitelisted set of .NET members, these LINQ extension methods are ignored or produce unexpected results. The recommended way to provide a fallback value in a template is still the null‑coalescing operator (??).

Why it happens
Aspose.Words Reporting Engine evaluates expressions inside a template against a predefined list of supported members. The list does not include the System.Linq.Enumerable extension methods such as Contains, Distinct or the FirstOrDefault overload that takes a default value. When such a method is used, the engine does not throw an error; it simply returns the original collection (or an empty string) which leads to the behaviour you observed.

What to do next

  1. Move LINQ logic to your code – Perform any filtering, distinct‑ification or default‑value handling before you pass the data object to the reporting engine.
var source = JsonConvert.DeserializeObject<MyRoot>(json);
// Example of using LINQ before the merge
source.MyList = source.MyList
.Where(item => item == "A") // replaces Contains
.Distinct()
.ToList();
var engine = new Aspose.Words.Reporting.ReportingEngine();
engine.BuildReport("aspose-test.docx", source, "output.docx");
  1. Create a custom user‑defined function (UDF) if you really need the logic inside the template.
var engine = new Aspose.Words.Reporting.ReportingEngine();
engine.RegisterFunction(
"ContainsItem",
(IEnumerable<object> collection, object value) =>
collection?.Contains(value) ?? false);
// In the template: {{ContainsItem(myList, 'A')}}

You can register similar functions for Distinct or a custom FirstOrDefaultWithDefault.
3. Use the null‑coalescing operator for defaults – This is the supported way to supply a fallback value directly in the template.

{{myValue ?? 'default text'}}
  1. Check the supported functions list – Consult the Aspose.Words Reporting Engine documentation (search for Supported Functions). It lists the members that can be used out‑of‑the‑box and confirms that the LINQ extension methods mentioned are not included.
  2. If you still see issues – Verify that you are indeed using ReportingEngine (not the simple Mail‑Merge API) and share the exact template line(s) and a small excerpt of the JSON data. With that information we can suggest a more precise UDF implementation.

Next steps

  • Pre‑process any LINQ‑based operations in your application code before calling BuildReport.
  • Register custom UDFs for any collection logic you need inside the template.
  • Continue using ?? for default values; there is no alternative built‑in function for this purpose.
  • Review the official documentation to avoid using unsupported members.

Note: The answer is based on the general behaviour of Aspose.Words Reporting Engine as described in its documentation. The provided sandbox logs do not contain explicit evidence for these specific methods, so please verify against the version of the SDK you are using.

:warning: I could not fully verify this explanation against the retrieved sources. Please double-check the behaviour in your environment and confirm the exact SDK/version.

@pgcentric

When it comes to working with JSON arrays of simple types, in order to access an item of such an array, it is required to use the name of the JSON array property plus “_Text” suffix everywhere, including built-in extension methods. Please try using the following tags in your template instead:

<<[node.arr2.select(i => i.arr2_Text).contains(6)]>>
<<foreach [item in node.arr2.select(i => i.arr2_Text).distinct()]>>
<<[item]>>
<</foreach>>

See Accessing JSON Data for more information.

Hi, distinct worked with the above syntax but contains still shows false even when 6 is present in the array. Could you please check?

<<[node.arr2.select(i => i.arr2_Text).contains(6)]>>

@pgcentric

contains uses Object.equals under the hood, so a strict type match is required. Please modify the template as follows:

<<[arr2.select(i => i.arr2_Text).contains(6L)]>>

Alternatively, you can use the following template syntax to utilize implicit type conversions:

<<[arr2.any(i => i.arr2_Text == 6)]>>