Skip to main content

Syncfusion SFDT Format

What is SFDT?​

SFDT (Syncfusion Document Format) is a JSON-based document representation format created by Syncfusion for their Document Editor component.

Why SFDT Exists​

  • Web-friendly: JSON is native to JavaScript
  • Easy manipulation: Modify documents in browser
  • Lightweight: Smaller than full OOXML XML
  • Editor optimized: Built for Syncfusion Document Editor

In AppraiSys​

SFDT is used as an intermediate format:

  1. Templates stored as DOCX
  2. Convert to SFDT for mail merge
  3. Manipulate in Syncfusion
  4. Convert back to DOCX for delivery

SFDT Structure Overview​

Complete SFDT Document​

{
"sections": [
{
"sectionFormat": {
"pageWidth": 612,
"pageHeight": 792,
"leftMargin": 72,
"rightMargin": 72,
"topMargin": 72,
"bottomMargin": 72
},
"blocks": [
{
"paragraphFormat": {},
"characterFormat": {},
"inlines": []
}
],
"headersFooters": {
"header": {},
"footer": {}
}
}
],
"characterFormat": {},
"paragraphFormat": {},
"defaultTabWidth": 36,
"trackChanges": false,
"enforcement": false,
"hashValue": "",
"saltValue": "",
"formatting": false,
"protectionType": "NoProtection",
"dontUseHTMLParagraphAutoSpacing": false,
"formFieldShading": true,
"styles": [],
"lists": [],
"abstractLists": [],
"comments": [],
"revisions": [],
"customXml": []
}

Sections​

Section Structure​

{
"sections": [
{
"sectionFormat": {
"pageWidth": 612, // 8.5 inches * 72 points
"pageHeight": 792, // 11 inches * 72 points
"leftMargin": 72, // 1 inch
"rightMargin": 72,
"topMargin": 72,
"bottomMargin": 72,
"differentFirstPage": false,
"different OddAndEvenPages": false,
"headerDistance": 36, // 0.5 inch
"footerDistance": 36,
"bidi": false
},
"blocks": [...],
"headersFooters": {}
}
]
}

Key properties:

  • All measurements in points (72 per inch)
  • Multiple sections supported
  • Headers/footers per section

Blocks (Paragraphs and Tables)​

Paragraph Block​

{
"paragraphFormat": {
"leftIndent": 0,
"rightIndent": 0,
"firstLineIndent": 0,
"textAlignment": "Left", // Left, Center, Right, Justify
"beforeSpacing": 0,
"afterSpacing": 8,
"lineSpacing": 1.15,
"lineSpacingType": "Multiple", // Multiple, AtLeast, Exactly
"styleName": "Normal",
"listFormat": {},
"bidi": false
},
"characterFormat": {
"bold": false,
"italic": false,
"fontSize": 11,
"fontFamily": "Calibri",
"fontColor": "#000000FF", // RGBA format
"fontFamilyBidi": "Calibri"
},
"inlines": [
{
"text": "Hello World",
"characterFormat": {}
}
]
}

Table Block​

{
"table": {
"rows": [
{
"cells": [
{
"blocks": [
{
"inlines": [
{ "text": "Cell content" }
]
}
],
"cellFormat": {
"cellWidth": 150,
"columnSpan": 1,
"rowSpan": 1,
"preferredWidth": 150,
"preferredWidthType": "Point",
"verticalAlignment": "Top",
"borders": {},
"shading": {}
}
}
],
"rowFormat": {
"height": 0,
"heightType": "AtLeast",
"isHeader": false,
"allowBreakAcrossPages": true
}
}
],
"grid": [150, 150], // Column widths
"tableFormat": {
"borders": {},
"cellSpacing": 0,
"leftIndent": 0,
"preferredWidth": 100,
"preferredWidthType": "Percent",
"bidi": false
}
}
}

Inlines (Content)​

Text Inline​

{
"text": "Sample text",
"characterFormat": {
"bold": true,
"italic": false,
"fontSize": 12,
"fontFamily": "Times New Roman",
"fontColor": "#000000FF",
"underline": "Single", // None, Single, Double, etc.
"strikethrough": "None",
"baselineAlignment": "Normal", // Superscript, Subscript
"highlightColor": "NoColor"
}
}

Line Break Inline​

{
"breakType": "LineBreak",
"breakClearType": "None"
}

Image Inline​

{
"imageString": "data:image/png;base64,iVBORw0KGgo...",
"width": 72, // Points
"height": 72,
"name": "image1.png",
"alternateText": "",
"title": "",
"visible": true,
"widthScale": 100,
"heightScale": 100,
"verticalPosition": 0,
"verticalOrigin": "Paragraph",
"verticalAlignment": "None",
"horizontalPosition": 0,
"horizontalOrigin": "Column",
"horizontalAlignment": "None",
"allowOverlap": true,
"textWrappingStyle": "Inline",
"textWrappingType": "Both",
"distanceBottom": 0,
"distanceLeft": 9,
"distanceRight": 9,
"distanceTop": 0,
"layoutInCell": true,
"lockAnchor": false,
"zOrderPosition": 0,
"isInlineImage": true
}

Image formats:

  • Base64 encoded: data:image/png;base64,...
  • URL: https://example.com/image.png

From AppraiSys (DocumentToPdfConverter.cs):

// Download images and convert to base64
var imagesToDownload = sfdtWrapper?.Images
.Where(x => x.Value.FirstOrDefault()?.StartsWith("http") ?? false)
.ToList();

foreach (var downloadedImage in downloadedImages)
{
sfdtWrapper.Images[key].Insert(0,
$"data:{contentType};base64,{Convert.ToBase64String(bytes)}"
);
}

Styles​

Paragraph Style​

{
"name": "Heading 1",
"type": "Paragraph",
"paragraphFormat": {
"leftIndent": 0,
"rightIndent": 0,
"firstLineIndent": 0,
"textAlignment": "Left",
"beforeSpacing": 12,
"afterSpacing": 6,
"lineSpacing": 1.15,
"lineSpacingType": "Multiple",
"outlineLevel": "Level1"
},
"characterFormat": {
"fontSize": 16,
"fontFamily": "Calibri Light",
"fontColor": "#2F5496FF",
"bold": false,
"italic": false
},
"basedOn": "Normal",
"link": "Heading 1 Char",
"next": "Normal"
}

Character Style​

{
"name": "Emphasis",
"type": "Character",
"characterFormat": {
"italic": true,
"fontColor": "#000000FF"
}
}

Lists and Numbering​

List Format​

{
"listFormat": {
"listId": 0,
"listLevelNumber": 0
}
}

List Definition​

{
"abstractListId": 0,
"levels": [
{
"characterFormat": {},
"paragraphFormat": {
"leftIndent": 36,
"firstLineIndent": -18
},
"followCharacter": "Tab",
"listLevelPattern": "Arabic", // Arabic, UpperRoman, LowerLetter, etc.
"numberFormat": "%1.",
"restartLevel": 0,
"startAt": 1
}
]
}

AppraiSys Usage Patterns​

Converting DOCX to SFDT​

From DocumentToSfdtFormat.cs:

public static string Convert(MemoryStream stream)
{
// Load DOCX using Syncfusion DocIO
var doc = new Syncfusion.DocIO.DLS.WordDocument(
stream,
Syncfusion.DocIO.FormatType.Docx
);

// Convert to SFDT using EJ2 DocumentEditor
var sfdtDocument = Syncfusion.EJ2.DocumentEditor.WordDocument.Load(doc);

// Optimize output
sfdtDocument.OptimizeSfdt = true;

// Serialize to JSON
var sfdt = Newtonsoft.Json.JsonConvert.SerializeObject(sfdtDocument);

// Cleanup
sfdtDocument.Dispose();
doc.Dispose();

return sfdt;
}

Converting SFDT to DOCX​

// From DocumentToPdfConverter.cs
var sfdtDocument = Syncfusion.EJ2.DocumentEditor.WordDocument.Save(
mergedSfdt,
Syncfusion.EJ2.DocumentEditor.FormatType.Docx
);

using var wordDocument = new Syncfusion.DocIO.DLS.WordDocument(
sfdtDocument,
Syncfusion.DocIO.FormatType.Docx
);

Mail Merge with SFDT​

From SyncfusionFieldsPipelineStep.cs:

// Document is in DOCX format
DestinationDocument = new WordDocument(input.DocumentData, FormatType.Docx);

// Configure mail merge
DestinationDocument.MailMerge.ClearFields = false;
DestinationDocument.MailMerge.BeforeClearField += BeforeClearFieldWrapper;

// Execute merge (internal conversion to SFDT happens)
DestinationDocument.MailMerge.Execute(new List<string> { "" });

SFDT Limitations and Bugs​

Known Issues (from AppraiSys experience)​

1. Blank Paragraph Artifacts​

Problem: Extra blank paragraphs appear after conversion

Cause: Syncfusion adds spacing paragraphs

Workaround: Clean empty blocks post-conversion

2. Section Break Oddities​

Problem: Section breaks don't convert cleanly

Cause: SFDT section model differs from OOXML

Workaround: Test section-heavy documents carefully

3. Image Embedding Issues​

Problem: External image URLs don't always work

Cause: Syncfusion requires base64 for reliable rendering

Solution: Download and encode images (as in AppraiSys)

4. Header/Footer Preservation​

Problem: Complex headers/footers may lose formatting

Cause: SFDT header model simplified

Workaround: Keep headers/footers simple

5. Content Control Flattening​

Problem: SDTs sometimes removed after conversion

Cause: Syncfusion mail merge behavior

Solution: Set ClearFields = false

6. TOC Refresh​

Problem: Table of contents doesn't update

Cause: SFDT doesn't support automatic TOC update

Workaround: Regenerate TOC in final DOCX

AppraiSys Patches​

From AppraiSys.Atlas.SyncfusionDep:

public static void PatchSyncfusion()
{
var harmony = new Harmony("appraisys.syncfusion.patches");

// Apply null guards for internal Syncfusion access
ApplyNullGuardsPatch(harmony);

// Remove embedded image versions
ApplyRemoveEmbeddedVersionsOfImagesPatch(harmony);
}

Purpose: Work around Syncfusion bugs using Harmony runtime patching


SFDT vs OOXML Comparison​

FeatureOOXMLSFDT
FormatXML in ZIPJSON
SizeLargerSmaller
Human readableSomewhatMore readable
Web friendlyNoYes
ImagesSeparate filesBase64 inline
Relationships.rels filesEmbedded
ValidationXML schemaJSON schema
EditingComplexEasier
Browser supportNoYes (with editor)

Working with SFDT Programmatically​

Parse SFDT JSON​

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

var sfdtObject = JObject.Parse(sfdtJson);

// Access sections
var sections = sfdtObject["sections"] as JArray;

// Access first block
var firstBlock = sections[0]["blocks"][0];

// Check if it's a paragraph
if (firstBlock["inlines"] != null)
{
// It's a paragraph
var inlines = firstBlock["inlines"] as JArray;
}
else if (firstBlock["table"] != null)
{
// It's a table
var table = firstBlock["table"];
}

Modify SFDT​

// Add text to first paragraph
var sections = sfdtObject["sections"] as JArray;
var firstBlock = sections[0]["blocks"][0];
var inlines = firstBlock["inlines"] as JArray;

inlines.Add(new JObject
{
["text"] = " Additional text",
["characterFormat"] = new JObject()
});

// Serialize back
var modifiedSfdt = sfdtObject.ToString();

Find Text in SFDT​

public List<string> FindAllText(JObject sfdt)
{
var texts = new List<string>();

foreach (var section in sfdt["sections"])
{
foreach (var block in section["blocks"])
{
if (block["inlines"] != null)
{
foreach (var inline in block["inlines"])
{
if (inline["text"] != null)
{
texts.Add(inline["text"].ToString());
}
}
}
}
}

return texts;
}

SFDT Best Practices​

✅ DO:​

  1. Optimize SFDT: Always set OptimizeSfdt = true
  2. Validate JSON: Parse to verify valid JSON
  3. Handle images: Convert to base64 for reliability
  4. Test conversions: Always round-trip test DOCX → SFDT → DOCX
  5. Clean empty blocks: Remove artifact paragraphs

❌ DON'T:​

  1. Don't edit SFDT manually: Use Syncfusion APIs
  2. Don't trust external image URLs: Download and encode
  3. Don't ignore validation errors: Fix structure issues
  4. Don't skip round-trip testing: Conversions can lose data
  5. Don't assume OOXML equivalence: Some features differ

Debugging SFDT​

Visualize Structure​

// In browser console
const sfdt = JSON.parse(sfdtString);
console.table(sfdt.sections[0].blocks.map((b, i) => ({
index: i,
type: b.inlines ? 'paragraph' : 'table',
content: b.inlines ? b.inlines.length + ' inlines' : b.table.rows.length + ' rows'
})));

Compare DOCX vs SFDT​

// 1. Convert DOCX to SFDT
var sfdt = DocumentToSfdtFormat.Convert(docxStream);

// 2. Parse and inspect
var sfdtObj = JObject.Parse(sfdt);
var blockCount = sfdtObj["sections"][0]["blocks"].Count();

Console.WriteLine($"SFDT has {blockCount} blocks");

// 3. Convert back and compare
var docx2Stream = ConvertSfdtToDocx(sfdt);
// Visual comparison in Word

SFDT Schema Reference​

Root Level​

{
"sections": [], // Required: Document sections
"characterFormat": {}, // Optional: Default character format
"paragraphFormat": {}, // Optional: Default paragraph format
"defaultTabWidth": 36, // Optional: Default tab width in points
"trackChanges": false, // Optional: Track changes enabled
"styles": [], // Optional: Style definitions
"lists": [], // Optional: List instances
"abstractLists": [], // Optional: List templates
"comments": [], // Optional: Comments
"revisions": [], // Optional: Tracked changes
"customXml": [] // Optional: Custom XML data
}

Quick Reference​

Common SFDT Modifications​

Change text color:

"characterFormat": {
"fontColor": "#FF0000FF" // Red (RGBA)
}

Center paragraph:

"paragraphFormat": {
"textAlignment": "Center"
}

Bold text:

"characterFormat": {
"bold": true
}

Add spacing:

"paragraphFormat": {
"beforeSpacing": 12,
"afterSpacing": 12
}

Next Steps​

➡️ Next: QA Testing Workflow

Related:


💡 Remember: SFDT is JSON-based and web-friendly, but has limitations compared to full OOXML. Test conversions thoroughly!