[SalesForce] pass Map as parametre from lwc to apex

I try to pass a map from my lwc to my Apex controller after when i click in button

every time I get null
Attempt to de-reference a null object

Do you have suggestions ? where i was wrong

if I want to put on a simple String it works.

 there is data in the map check with console.log on the javascript side

Controller Apex

 @AuraEnabled(cacheable=true)
public static String getresults(Map<String, String> myMap){
System.debug('myMap '+ myMap) ;
}

LWC Javascript
I tried like that

  import { LightningElement,wire,api,track } from 'lwc';
  import getresult from '@salesforce/apex/Controller.getresults';

export default class Ecranlwc extends LightningElement {
    **@track myMap = [];**
    buttonClick(event)
{

    var inp=this.template.querySelectorAll("lightning-input");
    inp.forEach(function(element){
if(element.name ==="name"){


            **this.myMap.push({key:"name",value:element.value});**
        }
  }
  console.log('myMap  ' + this.myMap) ;
  getresults({ myMap: myMap })
   .then(result => {
    console.log('result '+JSON.stringify(result));

   })
   .catch(error => {
       console.log('-------error -------------'+ JSON.stringify(error));
       console.log(error);
   });

}
}

And like that

  import { LightningElement,wire,api,track } from 'lwc';
  import getresult from '@salesforce/apex/Controller.getresults';

export default class EcranLWC extends LightningElement {

    buttonClick(event)
{

    var inp=this.template.querySelectorAll("lightning-input");
    **let myMap = new Map()**

    inp.forEach(function(element){
if(element.name ==="name"){

            **myMap.set('name',element.value);**
        }
  }
        console.log('myMap  ' + myMap) ;


  getresults({ myMap: myMap })
   .then(result => {
    console.log('result '+JSON.stringify(result));

   })
   .catch(error => {
       console.log('-------error -------------'+ JSON.stringify(error));
       console.log(error);
   });
 }
  }

Best Answer

There's a few things that I can see. For starters your first example has "map" defined as an array:

@track myMap = [];

Your second uses the JS Map object which will not map to anything in apex.

Apex Data structures

A customer can be represented as

Class

public class customer {
    public String FirstName {get; set;}
    public String LastName {get; set;}
}

Map

Map<String, Object> customer; // Notice that the second param is object, not string
// This allows for support of any data type as a value such as boolean or decimal

JS Maps

The JS Map object will not translate to any native objects in SF. I suppose in theory you could create a class in SF that might be able to work but thats much more work then necessary.

A map in Apex more closely translates to an Object in javascript. The Key in the map is the Name of the attribute in the Object and the value in the map is the value of the attribute. Lets use the below JS as an example:

// Map<String, Object> or a single Customer
var object = {'FirstName':'Bob','LastName':'smith'}; 

// Or  List<Map<String, Object>> or List<Customer>

var objects = [
    {'FirstName':'Bob','LastName':'smith'},                
    {'FirstName':'Fred','LastName':'Flintstone'}
];

// Or Map<String, Object> or Map<String, Customer>
var map = {
    "003XXXXXXXXXXXX": {..Customer object}
    "003XXXXXXXXXXXX": {..Customer object}
};

Passing data to apex

There are 2 ways to pass data like this to apex. First, is to pass it into an appropriately typed parameter

public static String getresults(Map<String, Object> customer)
public static String getresults(Customer customer)

// -- Or, for multiple customers --

public static String getresults(List<Map<String, Object>> customers)
public static String getresults(List<Customer> customers)

// -- Or for Map<Id, Customer>

public static String getResults(Map<String, Object> customers) {
    for (String recordId : customers.keySet()) {
        Map<String, Object> customer = (Map<String,Object>) customers.get(recordId);
        system.debug(customer.get('FirstName));
    }
}

public static String getResults(Map<String, Customer> customers) {
    // This one requires customer to be a top level class I believe
    // See my comments about stringifying below as it may work better
}

The second is to JSONify your data and pass it as a string

Javascript

var object = {'FirstName':'Bob','LastName':'smith'};
getResults(JSON.stringify(object));

Apex

public static String getresults(String customer) {
    // Typecast yourself
    Customer c = (Customer) JSON.deserialize(customer, Customer.class);
}

// Or

// For Map<Id, Customer>
public static String getResults(String customers) {
    Map<Id, Customer> = (Map<Id, Customer>) json.deserialize(customers, Map<Id, Customer>.class);
}

With aura components I believe that the second option was a more reliable approach as the built in serialization/deserialization was known to cause issues with some data types, including custom classes (I believe a custom class needs to be a top level class to work as a parameter but with this approach it can be a second level class).

Conclusion and TL;DR

The most reliable approach is to always pass your data as a JSON string and handle the deserialization on the apex side yourself. This does require you to know how to properly deserialize json in Apex.

Additional notes

JSON based REST services operate the same way. See this answer which I think tries to convey the same information but in the context of JSON REST services.

Related Topic