[SalesForce] Struggling to deserialize JSON with escaped double quotes

JSON(I am working with): JSONLinter confirms this is a valid JSON.

{
    "errors": [
        {
            "params": {
                "password": "size must be between 4 and 30",
                "loginId": "must match \"^[a-zA-Z0-9_]*$\""
            }
        }
    ]
}

Attempt 1(Using JSONParser:

String str = '{ "errors": [ { "params": { "password": "size must be between 4 and 30", "loginId": "must match \"^[a-zA-Z0-9_]*$\"" } } ] }';
JSONParser parser = JSON.createParser(str);
while(parser.nextToken() != null){
    parser.nextToken();
}

Fails:

▸ ERROR: System.JSONException: Unexpected character ('^' (code
94)): was ▸ expecting comma to separate OBJECT entries at input
location [1,99] ▸ ERROR: Class.System.JSONParser.nextToken: line
94, column 1 ▸ AnonymousBlock: line 3, column 1 ▸
AnonymousBlock: line 3, column 1

Attempt 2(JSON2Apex option):

public class JSON2Apex {

    public class Errors {
            public Params params;
    }

    public List<Errors> errors;

    public class Params {
            public String password;
            public String loginId;
    }

}

String str = '{ "errors": [ { "params": { "password": "size must be between 4 and 30", "loginId": "must match \"^[a-zA-Z0-9_]*$\"" } } ] }';
JSON2Apex obj = (JSON2Apex)JSON.deserialize(str, JSON2Apex.class);

Fails:

▸ ERROR: System.JSONException: Unexpected character ('^' (code
94)): was ▸ expecting comma to separate OBJECT entries at [line:1,
column:99] ▸ ERROR: Class.System.JSON.deserialize: line 15, column
1 ▸ AnonymousBlock: line 2, column 1 ▸ AnonymousBlock: line 2,
column 1

Attempt 3(deserializeUntyped):

String str = '{ "errors": [ { "params": { "password": "size must be between 4 and 30", "loginId": "must match \"^[a-zA-Z0-9_]*$\"" } } ] }';
Map<String,Object> jsonObj = (Map<String,Object>)JSON.deserializeUntyped(str);

Fails:

▸ ERROR: System.JSONException: Unexpected character ('^' (code
94)): was ▸ expecting comma to separate OBJECT entries at [line:1,
column:99] ▸ ERROR: Class.System.JSON.deserializeUntyped: line 11,
column 1 ▸ AnonymousBlock: line 2, column 1 ▸ AnonymousBlock:
line 2, column 1

Attempt 4(replace the problem causing double quote in JSON):

String str = '{ "errors": [ { "params": { "password": "size must be between 4 and 30", "loginId": "must match \"^[a-zA-Z0-9_]*$\"" } } ] }';
Map<String,Object> jsonObj = (Map<String,Object>)JSON.deserializeUntyped(str.replaceAll('\"', '\\"'));

Still fails:

▸ ERROR: System.JSONException: Unexpected character ('^' (code
94)): was ▸ expecting comma to separate OBJECT entries at [line:1,
column:99] ▸ ERROR: Class.System.JSON.deserializeUntyped: line 11,
column 1 ▸ AnonymousBlock: line 2, column 1 ▸ AnonymousBlock:
line 2, column 1

I have looked into the below similar questions but still don't see any solution:

  1. JSON and escaped double quote
  2. Replace double quotes with single quote in a JSON string

Best Answer

I'll try to illustrate this.

This is valid JSON.

{
    "errors": [
        {
            "params": {
                "password": "size must be between 4 and 30",
                "loginId": "must match \"^[a-zA-Z0-9_]*$\""
            }
        }
    ]
}

This is not:

String str = '{ "errors": [ { "params": { "password": "size must be between 4 and 30", "loginId": "must match \"^[a-zA-Z0-9_]*$\"" } } ] }';

Why? Because in Apex, backslash is an escape character in a string literal. If you want that backslash to carry through to the in-memory representation of the string (your JSON), you must escape it in your source code:

String str = '{ "errors": [ { "params": { "password": "size must be between 4 and 30", "loginId": "must match \\"^[a-zA-Z0-9_]*$\\"" } } ] }';

Otherwise, Apex removes it, yielding an in-memory string that is not valid JSON because nested quote marks are not escaped:

"loginId": "must match "^[a-zA-Z0-9_]*$"" 

You don't need to use replaceAll() or anything fancy to fix this (and in fact, you can't - by the time you could call replaceAll(), the string's already broken and has no backslashes in it).

The problem is not in your logic. It's just in how you're writing string literals in your source code.