1

I have a string which I am trying to deserialize to a wrapperclass / object. But get the error:

Expected Map<String,String> but found [line:1, column:3]

Wrapperclass

global class Invocable_Obj_HandleInvoice  {
    @AuraEnabled global Id invoiceId;
    @AuraEnabled global string invoiceNumber;
    @AuraEnabled global decimal balanceAmount;
    @AuraEnabled global decimal totalAmount;
    @AuraEnabled global string customerName;
    @AuraEnabled global string zuoraStatus;
    @AuraEnabled global string handlingStatus;
    @AuraEnabled global string selectedOption;
    @AuraEnabled global Map<String, String> availableOptions;
}

String

[
  {
    "availableOptions": [
      {
        "key": "e",
        "value": "f"
      },
      {
        "key": "g",
        "value": "z"
      }
    ],
    "balanceAmount": -100,
    "customerName": "Test",
    "handlingStatus": "New",
    "invoiceId": "a0S2o000023CSHoEAO",
    "invoiceNumber": "INV-001",
    "selectedOption": "A",
    "totalAmount": -100,
    "zuoraStatus": "Posted"
  },
  {
    "availableOptions": [
      {
        "key": "a",
        "value": "x"
      },
      {
        "key": "b",
        "value": "y"
      },
      {
        "key": "c",
        "value": "z"
      }
    ],
    "balanceAmount": -100,
    "customerName": "Test",
    "handlingStatus": "New",
    "invoiceId": "a0S2o000023CSHoEAO",
    "invoiceNumber": "INV-001",
    "selectedOption": "y",
    "totalAmount": -100,
    "zuoraStatus": "Posted"
  }
]

Apex

 system.debug((List<Invocable_Obj_HandleInvoice>) System.JSON.deserialize(invoices, List<Invocable_Obj_HandleInvoice>.class));  

List passed to lightning component

 List<Invocable_Obj_HandleInvoice> hiList = new List<Invocable_Obj_HandleInvoice>();
    Invocable_Obj_HandleInvoice  hi1 =  new Invocable_Obj_HandleInvoice(); 
    hi1.invoiceId = zi[0].id;
    hi1.invoiceNumber = zi[0].Name; 
    hi1.balanceAmount = zi[0].Zuora__Balance2__c;
    hi1.totalAmount = zi[0].Zuora__TotalAmount__c;
    hi1.customerName = 'Test';
    hi1.zuoraStatus = zi[0].Zuora__Status__c; 
    hi1.handlingStatus = zi[0].Handling_Status__c;
    hi1.selectedOption = 'A'; 
    hi1.availableOptions = new Map<String,string> {'e'=>'f', 'g'=> 'z'}; 
    hiList.add(hi1); 
return hiList;

Lightning controller

var act = cmp.get("c.updateInvoices");
var updatedInvoiceList = JSON.stringify(cmp.get('v.handleInvoices'));
act.setParams({ "invoices" : updatedInvoiceList});
$A.enqueueAction(act); 

Component <aura:attribute type="List" name="handleInvoices"/>

Lightning controller

doInit : function(cmp, event, helper) {
        var action = cmp.get("c.testHandlInvoice");
        
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                cmp.set('v.handleInvoices', response.getReturnValue());
                console.log(cmp.get('v.handleInvoices'));
            }
            
        });
        $A.enqueueAction(action);
    },

console.log

availableOptions: Array(3)
0: {key: "a", value: "x"}
1: {key: "b", value: "y"}
2: {key: "c", value: "z"}
length: 3
__proto__: Array(0)
balanceAmount: -100
customerName: "Test"
handlingStatus: "New"
invoiceId: "a0S2o000023CSHoEAO"
invoiceNumber: "INV-001"
selectedOption: "A"
totalAmount: -100
zuoraStatus: "Posted"

UPDATE***** For anyone bumping into this issue I figured out the answer below addresses the issue of the map but after stringify the map is still altered so I ended combining this solution passing the object directly: How to pass an object from a Lightning Component helper to an Apex Controller method

  var act = cmp.get("c.updateInvoices");
        var tempInvoiceList = cmp.get('v.handleInvoices');
        tempInvoiceList = tempInvoiceList.slice(); // copy
        tempInvoiceList = tempInvoiceList.map(function(invoice) { return Object.assign({}, invoice); }); // copy deeper
        act.setParams({ "invoices" : tempInvoiceList});
        tempInvoiceList = tempInvoiceList
        .forEach(function(invoice) { 
            invoice.availableOptions = invoice.availableOptions
            .reduce(function(prev, option) { 
                prev[option.key] = option.value; 
                return prev;
            }, 
                    {})
        }
                );
        $A.enqueueAction(act); 

2 Answers 2

3
@AuraEnabled global Map<String, String> availableOptions;

Should be:

@AuraEnabled global List<Map<String,String>> availableOptions;

Or:

@AuraEnabled global List<KeyValuePair> availableOptions;

Where KeyValuePair is:

public class KeyValuePair {
  @AuraEnabled public String key;
  @AuraEnabled public String value;
}

Which is used commonly enough in code that I actually have that as a top-level class in my developer org; I use it quite frequently for picklist values and so on.

This sort of JSON is generally discouraged, but if you have to use it that way, this is the preferred form. In order to use your original Apex, it would need to be formatted as:

"availableOptions": { "e": "f", "g", "z" },

Which is how JSON is meant to be formatted in the general sense, although the previous form is acceptable for cases like picklist options, which have a value and a label.


It looks like you need to transform your "availableOptions" back to a proper map to be used correctly:

var act = cmp.get("c.updateInvoices");
var tempInvoiceList = cmp.get('v.handleInvoices');
tempInvoiceList = tempInvoiceList.slice(); // copy
tempInvoiceList = tempInvoiceList.map(function(invoice) { return Object.assign({}, invoice); }); // copy deeper
tempInvoiceList = tempInvoiceList
  .forEach(function(invoice) { 
    invoice.availableOptions = invoice.availableOptions
      .reduce(function(prev, option) { 
         prev[option.key] = option.value; 
         return prev;
      }, 
      {})
    }
  );
var updatedInvoiceList = JSON.stringify(tempInvoiceLIst);
act.setParams({ "invoices" : updatedInvoiceList});
$A.enqueueAction(act); 
8
  • If I would assume the wrapperclass is correct, wouldnt there be another way to get the string deserialized as the wrapper?
    – Thomas
    Aug 18, 2020 at 13:18
  • @Thomas If the wrapperclass is correct, the JSON is incorrect. Unless you want to write your own parser by hand (JSONParser class), you need to change either the Apex or JSON to match each other.
    – sfdcfox
    Aug 18, 2020 at 13:38
  • Please bear with me, I updated my question with more info. I added the list I pass from apex to my lightning component is do an update of a field on the server side and send the list back to apex. I understood I need to stringify first to be able to send the list back. Where in the proces do I convert the map to a list or do I need to approach this in a different way, sice I am not altering the list from apex in the lightning controller I only update a field with the selection.
    – Thomas
    Aug 18, 2020 at 14:11
  • @Thomas I don't see how the data was transformed from {"e":"f","g":z"} to [{"key":"e","value":"f"},{"key":"g","value":"z"}]. Is this happening elsewhere in your code? If so, you need to reverse the mapping with Array.prototype.reduce. I'll edit.
    – sfdcfox
    Aug 18, 2020 at 14:28
  • Thanks for your reply to make the issue clearer I added the console.log. It seems that when I pass the list from apex to the controller the map is altered to a list. I dont have any clue why?
    – Thomas
    Aug 18, 2020 at 14:48
0

availableOptions in your JSON is not a map, it's a list of objects (with fields "key" and "value").

So your wrapper class needs to reflect that. Instead of a Map<String, String>, you need to make it a List of another class/type that can hold the target data ("key" and "value")

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .