[SalesForce] replaceAll() \r\n in a JSON serialized string

I can't get replaceAll to work on the '\r\n' sequence in a JSON serialized string.

I am serializing a Map object that contains long textarea fields with newlines. I need to change '\r\n' to '\\r\\n' for Javascript purposes. When I serialize the object, I see '\r\n' in the resulting string. However, none of the replaceAll lines that I try will work:

Map<Id, Job_Posting__c> jobMap = new Map<Id, Job_Posting__c>();
// job map gets populated.
String x = JSON.serialize(jobMap);
String y = x.replaceAll('\r\n', '\\r\\n');
String z = x.replaceAll('\\r\\n', '\\\\r\\\\n');

Is there something about JSON serialized strings that force a different approach? I see that the serialized string is between double quotes – does that make a difference?

Details: I want to pass a serialized map of job posting records to a Javascript function in a Visualforce page. The Javascript JSON.parse() fails with "unexpected token" when it hits the '\r\n'. If I manually edit the JSON string to be '\\r\\n' then the parse works.

Here's the Javascript on the VF page:

<script>
    // Define the CCx.pageJobSelection module - can be moved to static resource
    (function (module, $) {
        "use strict";
        var data_jobPostings;

        // intercept the 'submit' action of the HTML form 
        jQuery(document).ready( function ($) {
            //processing here.
        });

        module.initialize = function (options) {
            data_jobPostings = JSON.parse(options.jobPostings);
        };

    })(CCx.pageJobSelection = CCx.pageJobSelection || {}, jQuery);

    // initialize the module
    CCx.pageJobSelection.initialize({
        jobPostings: '{!jobMapJSON}'
    })

</script>

Here's the VF controller:

public with sharing class HL_CCx_JobSelectionController {
    public String jobMapJSON { get; private set; }
    public List<Job_Posting__c> jobs { get; private set; }
    private Map<Id, Job_Posting__c> jobMap = new Map<Id, Job_Posting__c>();

    // Constructor
    public HL_CCx_JobSelectionController() {
        jobs = CCx_JobSelector.selectActiveJobs();
        if (jobs == null) {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL, 'No jobs postings available.'));
            return;
        }

        jobMap.putAll(jobs);

        // Serialize
        jobMapJSON = JSON.serialize(jobMap);
        jobMapJSON = jobMapJSON.replaceAll('\r\n', '\\r\\n');
    }
}

Best Answer

Why not just emit the JSON into the page so it is immediately parsed by JavaScript:

CCx.pageJobSelection.initialize(
    {!jobMapJSON}
);

and then there is no need to parse yourself:

module.initialize = function (options) {
    data_jobPostings = options;
};

This should avoid the need for any replacing.

Its also worth using JSON.serializePretty to make the JSON easier to manually review when you do a "View Page Source" of the page.

PS

This page:

<apex:page controller="AbcController">
<script>
var map = {!jsonString};
alert(map);
</script>
</apex:page>

and controller (where cve__OccupationalDescription__c is a custom text area field):

public class AbcController {
    public String jsonString {
        get {
            return JSON.serializePretty(new Map<Id, Contact>([
                    select Id, FirstName, LastName, cve__OccupationalDescription__c
                    from Contact
                    limit 2
                    ]));
        }
    }
}

produce this valid JSON that the browser's JavaScript engine is happy with:

<script>
var map = {
  "003A000000ou3XLIAY" : {
    "attributes" : {
      "type" : "Contact",
      "url" : "/services/data/v30.0/sobjects/Contact/003A000000ou3XLIAY"
    },
    "cve__OccupationalDescription__c" : "Line 1.\r\nLine 2.\r\nLine 3.",
    "RecordTypeId" : "012A0000000zC62IAE",
    "FirstName" : "Tim",
    "Id" : "003A000000ou3XLIAY",
    "LastName" : "Jones"
  },
  "003A000000pm2plIAA" : {
    "attributes" : {
      "type" : "Contact",
      "url" : "/services/data/v30.0/sobjects/Contact/003A000000pm2plIAA"
    },
    "RecordTypeId" : "012A0000000zC62IAE",
    "FirstName" : "Derek",
    "Id" : "003A000000pm2plIAA",
    "LastName" : "O'Brien"
  }
};
alert(map);
</script>