JSON and XML
Data that arrives as text needs no classes in your application: a JSON string from a web service goes straight into MailMerge, and an XML file takes one extra step through a DataSet. This applies whether the WPF application receives the data, reads it from disk or lets users open a file in the Reporting tab. The sections below give the rules of each format, the exceptions for invalid JSON and the cultures that parse numbers and dates written as text.
The examples use invoice.json of the Getting Started tutorial Create a Template and a window with a TextControl named textControl1 that shows the template invoice.docx.
Merge JSON Data
MergeJsonData takes the JSON as a string and analyzes it like MergeObjects analyzes objects:
- One object or an array. The JSON holds a single object or an array of objects with the same structure, and the template is merged once per object.
- The first object defines the structure. Tables, columns and relations come from the property names and nested objects of the object, or of the first object in the array.
- Nested objects and arrays. A nested object provides values for fields with dot notation, such as
Customer.City, and an array the records of the merge block with its name, such asItems. - Images. An image placeholder can receive an image given as a Base64 string, as Images and Barcodes describes.
Invalid JSON makes MergeJsonData throw an exception derived from ParserException in the TXTextControl.DocumentServer.Json namespace:
| Exception | Cause |
|---|---|
| UnexpectedEndOfInputException | The JSON string is incomplete |
| UnexpectedTokenException | The JSON contains an unexpected character |
| InvalidNumberFormatException | A number has an invalid format |
| InvalidCodePointException | A hexadecimal Unicode code point is invalid |
| UnknownLiteralNameTokenException | An unknown character sequence stands where true, false or null is expected |
| UnknownParseErrorException | The parser encountered another error |
Catching the base class handles every case. In a button's Click handler in MainWindow.xaml.cs:
using System.IO;
using System.Windows;
using TXTextControl.DocumentServer;
using TXTextControl.DocumentServer.Json;
private void mergeJsonButton_Click(object sender, RoutedEventArgs e)
{
using (MailMerge mailMerge = new MailMerge { TextComponent = textControl1 })
{
try
{
mailMerge.MergeJsonData(File.ReadAllText("invoice.json"));
}
catch (ParserException exception)
{
MessageBox.Show(this, $"Invalid JSON ({exception.GetType().Name}): {exception.Message}");
}
}
}
With valid data, the editor shows the merged invoice. Remove a closing bracket from invoice.json to see the message with the name of the exception.
Merge XML Data
XML goes through a DataSet: DataSet.ReadXml reads the file, and Merge merges its master table, as for any DataSet. The invoice as invoice.xml, copied to the output folder:
<Invoices>
<Invoice>
<Company>Contoso Ltd.</Company>
<Name>Jane Miller</Name>
<InvoiceNumber>INV-1001</InvoiceNumber>
<Items>
<Description>Consulting</Description>
<Quantity>2</Quantity>
<Price>150.00</Price>
</Items>
<Items>
<Description>Training</Description>
<Quantity>1</Quantity>
<Price>480.00</Price>
</Items>
</Invoice>
</Invoices>
From the repeated elements, ReadXml creates the tables Invoice and Items. Because Items is nested in Invoice, it adds a key column Invoice_Id to both and a relation between them, which gives the Items block the items of each invoice:
using System.Data;
using System.Globalization;
using System.Windows;
using TXTextControl.DocumentServer;
private void mergeXmlButton_Click(object sender, RoutedEventArgs e)
{
// Nested elements become related tables
DataSet dataSet = new DataSet();
dataSet.ReadXml("invoice.xml");
using (MailMerge mailMerge = new MailMerge
{
TextComponent = textControl1,
// The prices in the XML file are written the US English way
DataSourceCulture = CultureInfo.GetCultureInfo("en-US")
})
{
mailMerge.Merge(dataSet.Tables["Invoice"], true);
}
}
Flat XML, whose tables are linked by key columns instead of nesting, needs relations added after ReadXml, as DataSets and DataTables shows. Microsoft's Inferring Relationships documents how ReadXml derives relations.
Parse Numbers and Dates With the Right Culture
Without a schema, ReadXml reads every value as a string, and JSON often carries dates as strings. MailMerge parses numbers and dates from the data with the culture in DataSourceCulture and formats them in the document with the culture in MergeCulture. Both default to the culture of the current thread, which follows the regional settings of the user's Windows account, so the same file can merge differently on two computers. Set them explicitly when the data has a fixed format:
mailMerge.DataSourceCulture = new CultureInfo("de-DE"); // how the data writes numbers
mailMerge.MergeCulture = new CultureInfo("en-US"); // how the document shows them
The properties must be set before the merge method is called. Merge Fields describes the same two properties for a single field.
Load JSON and XML Into the Reporting Tab
In the Select Data Source drop-down of the WPF Reporting tab, Load XML File and Load JSON File let users open a data file for template design; Data Sources shows the drop-down. In code, the DataSourceManager of the tab loads the same data: LoadJson a JSON string, LoadXmlFile an XML file and LoadXmlString XML as a string. Connect the handler with Loaded="textControl1_Loaded" on the TextControl element:
private void textControl1_Loaded(object sender, RoutedEventArgs e)
{
ribbonReportingTab1.DataSourceManager.LoadXmlFile("invoice.xml");
}
A few records with the structure of the real data fill the drop-downs. The template designed with them merges with any data of that structure, as JSON, XML, objects or a DataSet.
Next Steps
- Objects and Collections: the rules
MergeJsonDatashares with business objects. - DataSets and DataTables: relations for flat tables.
- Merge Fields: numeric and date formats of single fields.
- Merge Blocks: the blocks that arrays fill.
- The blog article MailMerge: Data Structures Explained with a Sample Template and JSON Data: nested JSON data and the template it fills.