Intelligent Tax Document File Download
Add Embedded FDX JSON to Your PDFs
At taxdataexchange.org this is referred to as "Intelligent Tax Documents"
The PDF files contain the tax document data in custom properties. This technology allows simple, reliable, data extraction.
Example Intelligent Tax Document
How to Implement
See the below example code.
The below code - plus more - is available for free at this public Bitbucket repository.
Requirements for Tax Document Issuers
For a typical tax form, just ~ 120 lines of code and ~ 24 hours of coding will be required.
A relatively small investment on your part can save thousands of hours for your tax document recipients.
Incremental development required:
- Map data to FDX TaxStatement object
- Serialize TaxStatement object to JSON
- Embed the JSON into existing PDF document
1. Map data to FDX TaxStatement object
The below code - plus more - is available at no charge at this public Bitbucket repository.
package sample;
import org.joda.time.LocalDate;
import org.openapitools.client.model.*;
import java.math.BigDecimal;
import java.util.logging.Logger;
public class SamplePdfWithJson {
public static Logger LOGGER
= Logger.getLogger( SamplePdfWithJson.class.getName( ) );
private TaxStatement marshallData() {
TaxStatement taxStatement = new TaxStatement( );
TaxData taxData = new TaxData( );
Tax1098 form = new Tax1098( );
// =========================================================
// Tax base class values
// =========================================================
// Simple Integer taxYear
Integer taxYear = 2022;
form.setTaxYear( taxYear );
// Simple Boolean corrected
boolean corrected = false;
form.setCorrected( corrected );
// Simple String accountId
String accountId = "accountId";
form.setAccountId( accountId );
// Simple String taxFormId
String taxFormId = "taxFormId";
form.setTaxFormId( taxFormId );
// Simple LocalDate taxFormDate
LocalDate taxFormDate = new LocalDate( );
form.setTaxFormDate( taxFormDate );
// =========================================================
// String values
// =========================================================
// RECIPIENT'S/LENDER'S TIN
String lenderTin = "lenderTin";
// form.setLenderTin( lenderTin );
// PAYER'S/BORROWER'S TIN
String borrowerTin = "borrowerTin";
// form.setBorrowerTin( borrowerTin );
// Other information
String otherInformation = "otherInformation";
form.setOtherInformation( otherInformation );
// Account number
String accountNumber = "accountNumber";
form.setAccountNumber( accountNumber );
// Description of property securing mortgage, if property securing mortgage has no address
String propertyDescription = "propertyDescription";
form.setPropertyDescription( propertyDescription );
// =========================================================
// Date values
// =========================================================
// Box 3, Mortgage origination date
LocalDate originationDate = new LocalDate( );
form.setOriginationDate( originationDate );
// Box 11, Mortgage acquisition date
LocalDate acquisitionDate = new LocalDate( );
form.setAcquisitionDate( acquisitionDate );
// =========================================================
// Boolean values
// =========================================================
// Box 7, Is address of property securing mortgage same as PAYER'S/BORROWER'S address
Boolean isPropertyAddressSameAsBorrowerAddress = true;
form.setIsPropertyAddressSameAsBorrowerAddress( isPropertyAddressSameAsBorrowerAddress );
// =========================================================
// Double values
// =========================================================
// Box 1, Mortgage interest received from borrower
BigDecimal mortgageInterest = new BigDecimal( 1000.00 );
form.setMortgageInterest( mortgageInterest );
// Box 2, Outstanding mortgage principal
BigDecimal outstandingPrincipal = new BigDecimal( 1000.00 );
form.setOutstandingPrincipal( outstandingPrincipal );
// Box 4, Refund of overpaid interest
BigDecimal overpaidRefund = new BigDecimal( 1000.00 );
form.setOverpaidRefund( overpaidRefund );
// Box 5, Mortgage insurance premiums
BigDecimal mortgageInsurance = new BigDecimal( 1000.00 );
form.setMortgageInsurance( mortgageInsurance );
// Box 6, Points paid on purchase of principal residence
BigDecimal pointsPaid = new BigDecimal( 1000.00 );
form.setPointsPaid( pointsPaid );
// Property tax
BigDecimal propertyTax = new BigDecimal( 1000.00 );
form.setPropertyTax( propertyTax );
// =========================================================
// Integer values
// =========================================================
// Box 9, Number of properties securing the mortgage
int mortgagedProperties = 2000;
form.setMortgagedProperties( mortgagedProperties );
// =========================================================
// Other types
// =========================================================
{
// Lender's name and address
NameAddressPhone obj = new NameAddressPhone();
// Populate values as applicable
// NameAddressPhone
// Phone number TelephoneNumberPlusExtension
TelephoneNumberPlusExtension o_phone = new TelephoneNumberPlusExtension( );
{
// TelephoneNumberPlusExtension
// An arbitrary length telephone number extension string
String o_extension = "extension";
o_phone.setExtension( o_extension );
// TelephoneNumber
// Type of phone number: HOME, BUSINESS, CELL, FAX TelephoneNumberType
TelephoneNumberPurpose o_type = null;
o_phone.setType( o_type );
// Country calling codes defined by ITU‐T recommendations E.123 and E.164 string
String o_country = "country";
o_phone.setCountry( o_country );
// Telephone subscriber number defined by ITU-T recommendation E.164 string
String o_number = "number";
o_phone.setNumber( o_number );
}
obj.setPhone( o_phone );
// NameAddress
// Name line 1 String64
String o_name1 = "name1";
obj.setName1( o_name1 );
// Name line 2 String64
String o_name2 = "name2";
obj.setName2( o_name2 );
// Address
// Address line 1 String64
String o_line1 = "line1";
obj.setLine1( o_line1 );
// Address line 2 String64
String o_line2 = "line2";
obj.setLine2( o_line2 );
// Address line 3 String64
String o_line3 = "line3";
obj.setLine3( o_line3 );
// City String64
String o_city = "city";
obj.setCity( o_city );
// State or province String64
String o_state = "state";
obj.setRegion( o_state );
// Postal code string
String o_postalCode = "postalCode";
obj.setPostalCode( o_postalCode );
// Country code Iso3166CountryCode
Iso3166CountryCode o_country = null;
obj.setCountry( o_country );
// form.setLenderNameAddress( obj );
}
{
// Borrower's name and address
NameAddress obj = new NameAddress();
// Populate values as applicable
// NameAddress
// Name line 1 String64
String o_name1 = "name1";
obj.setName1( o_name1 );
// Name line 2 String64
String o_name2 = "name2";
obj.setName2( o_name2 );
// Address
// Address line 1 String64
String o_line1 = "line1";
obj.setLine1( o_line1 );
// Address line 2 String64
String o_line2 = "line2";
obj.setLine2( o_line2 );
// Address line 3 String64
String o_line3 = "line3";
obj.setLine3( o_line3 );
// City String64
String o_city = "city";
obj.setCity( o_city );
// State or province String64
String o_state = "state";
obj.setRegion( o_state );
// Postal code string
String o_postalCode = "postalCode";
obj.setPostalCode( o_postalCode );
// Country code Iso3166CountryCode
Iso3166CountryCode o_country = null;
obj.setCountry( o_country );
// form.setBorrowerNameAddress( obj );
}
{
// Address of property securing mortgage
Address obj = new Address();
// Populate values as applicable
// Address
// Address line 1 String64
String o_line1 = "line1";
obj.setLine1( o_line1 );
// Address line 2 String64
String o_line2 = "line2";
obj.setLine2( o_line2 );
// Address line 3 String64
String o_line3 = "line3";
obj.setLine3( o_line3 );
// City String64
String o_city = "city";
obj.setCity( o_city );
// State or province String64
String o_state = "state";
obj.setRegion( o_state );
// Postal code string
String o_postalCode = "postalCode";
obj.setPostalCode( o_postalCode );
// Country code Iso3166CountryCode
Iso3166CountryCode o_country = null;
obj.setCountry( o_country );
form.setPropertyAddress( obj );
}
taxData.setTax1098( form );
taxStatement.addFormsItem( taxData );
return taxStatement;
}
public static void main(String[] args) {
System.out.println(
"Begin"
);
SamplePdfWithJson samplePdfWithJson = new SamplePdfWithJson( );
// Marshall the form data
TaxStatement taxStatement = samplePdfWithJson.marshallData( );
// Serialize data to JSON
String json = TaxStatementSerializer.serialize( taxStatement );
// Display
System.out.println( json );
// Starting PDF
String existingPdfFile = "blank.pdf";
// Embed JSON
// And write out PDF with embedded JSON
String newPdfFile = "embedded-json.pdf";
FdxJsonInjector.inject(
existingPdfFile,
newPdfFile,
taxStatement
);
System.out.println(
"Done"
);
}
}
2. Serialize TaxStatement object to JSON
package sample;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import org.openapitools.client.model.TaxStatement;
import java.io.IOException;
import java.io.StringWriter;
import java.util.logging.Logger;
public class TaxStatementSerializer {
public static Logger LOGGER = Logger.getLogger( TaxStatementSerializer.class.getName( ) );
private String issue;
public TaxStatementSerializer( ) {
}
public String getIssue( ) {
return issue;
}
public TaxStatement parse(
String json
) {
TaxStatement data = null;
try {
ObjectMapper objectMapper = new ObjectMapper( );
// Special handling for dates
objectMapper.registerModule( new JodaModule( ) );
objectMapper.disable( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS );
data = objectMapper.readValue( json, TaxStatement.class );
} catch( Exception e ) {
LOGGER.severe( e.getMessage( ) );
this.issue = e.getMessage( );
}
return data;
}
public static TaxStatement deserialize(
String json
) {
TaxStatement data = null;
try {
ObjectMapper objectMapper = new ObjectMapper( );
// Special handling for dates
objectMapper.registerModule( new JodaModule( ) );
objectMapper.disable( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS );
data = objectMapper.readValue( json, TaxStatement.class );
} catch( Exception e ) {
LOGGER.severe( e.getMessage( ) );
}
return data;
}
public static String serialize(
TaxStatement data
) {
String json = "";
try {
StringWriter sw = new StringWriter( );
ObjectMapper objectMapper = new ObjectMapper( );
// Special handling for dates
objectMapper.registerModule( new JodaModule( ) );
objectMapper.disable( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS );
// Indentation recommended but not required
objectMapper.configure( SerializationFeature.INDENT_OUTPUT, true );
// Do not include null values
objectMapper.setSerializationInclusion( JsonInclude.Include.NON_NULL );
objectMapper.writeValue( sw, data );
json = sw.toString( );
} catch ( JsonGenerationException e ) {
LOGGER.severe( e.getMessage( ) );
} catch ( JsonMappingException e ) {
LOGGER.severe( e.getMessage( ) );
} catch ( IOException e ) {
LOGGER.severe( e.getMessage( ) );
}
return json;
}
public static String serializeCompact(
TaxStatement data
) {
String json = "";
try {
StringWriter sw = new StringWriter( );
ObjectMapper objectMapper = new ObjectMapper( );
// Special handling for dates
objectMapper.registerModule( new JodaModule( ) );
objectMapper.disable( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS );
// Indentation - not in this case
objectMapper.configure( SerializationFeature.INDENT_OUTPUT, false );
// Do not include null values
objectMapper.setSerializationInclusion( JsonInclude.Include.NON_NULL );
objectMapper.writeValue( sw, data );
json = sw.toString( );
} catch ( JsonGenerationException e ) {
LOGGER.severe( e.getMessage( ) );
} catch ( JsonMappingException e ) {
LOGGER.severe( e.getMessage( ) );
} catch ( IOException e ) {
LOGGER.severe( e.getMessage( ) );
}
return json;
}
}
3. Insert the JSON into existing PDF document
package sample;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
import org.openapitools.client.model.FdxVersion;
import org.openapitools.client.model.TaxStatement;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Calendar;
import java.util.logging.Logger;
public class FdxJsonInjector {
public static Logger LOGGER
= Logger.getLogger( FdxJsonInjector.class.getName( ) );
public static final FdxVersion FDX_VERSION_ENUM = FdxVersion.V6_5_0;
public static final String FDX_VERSION = FDX_VERSION_ENUM.getValue( );
public static final String SOFTWARE_ID = "YourSoftwareId";
/**
* Inject FDX JSON, etc. into PDF
* @param pdfBytes PDF as byte array
* @param taxStatement FDX object
* @return PDF as byte array
*/
public static byte[] inject(
byte[] pdfBytes,
TaxStatement taxStatement
) {
if ( pdfBytes == null ) return new byte[]{};
if ( pdfBytes.length < 1 ) return new byte[]{};
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream( );
try(
PDDocument doc = PDDocument.load( pdfBytes )
) {
inject(
doc,
taxStatement
);
doc.save( byteArrayOutputStream );
} catch ( Exception e ) {
LOGGER.severe( e.getMessage( ) );
}
return byteArrayOutputStream.toByteArray( );
}
public static void inject(
String pdfInFilePath,
String pdfOutFilePath,
TaxStatement taxStatement
) {
try (
PDDocument doc = PDDocument.load( new File( pdfInFilePath ) )
) {
inject(
doc,
taxStatement
);
doc.save( new File( pdfOutFilePath ) );
} catch ( IOException e ) {
LOGGER.severe( e.getMessage( ) );
}
}
/**
* Inject FDX JSON, etc. into PDF document
* @param doc PDF as document
* @param taxStatement FDX object
*/
public static void inject(
PDDocument doc,
TaxStatement taxStatement
) {
String fdxJson = TaxStatementSerializer.serialize( taxStatement );
// =======================================================
// Set document information
// =======================================================
PDDocumentInformation documentInformation = doc.getDocumentInformation( );
if ( documentInformation == null ) {
documentInformation = new PDDocumentInformation();
}
documentInformation.setCreationDate( Calendar.getInstance( ) );
documentInformation.setModificationDate( Calendar.getInstance( ) );
documentInformation.setCustomMetadataValue(
"fdxJson",
fdxJson
);
documentInformation.setCustomMetadataValue(
"fdxVersion",
FDX_VERSION
);
documentInformation.setCustomMetadataValue(
"fdxSoftwareId",
SOFTWARE_ID
);
doc.setDocumentInformation( documentInformation );
}
}