Search code examples
flutterdartpass-by-value

Pass Map object without reference to original object in Dart


I'm working on a Flutter/Dart application where I need to pass a Map object from one screen to another. However, I want to ensure that the original Map object remains unchanged and that any modifications made to the passed Map object on the second screen do not affect the original Map object on the first screen. Original Map object contains JSON init so it will be not clear structure of it.

Map originalMap = {
  'question': 'What is your name',
};
Map modifiedMap = {
  'question': 'What is your name',
  'answer': 'XYZ',
};

I've considered using methods like copyWith or creating a new Map object from the original Map.

Map modifiedMap = new Map.from(originalMap);
Map modifiedMap = JSON.decode(JSON.encode(originalMap));

Any guidance or examples would be greatly appreciated. Thank you!


Solution

  • What you are looking for is performing a deep copy of your JSON object.

    Deep copy

    JSON objects, when represented as Dart objects are of type Map<String, dynamic>, where according to JSON specification the datatype for the dynamic are:

    • string
    • number
    • boolean
    • null
    • object (JSON object => Map<String, dynamic>)
    • array

    Your deep copy function shall consider all the types in order to also copy the nested objects, which is not performed when using Map.from or List.from constructors.

    Map<String, dynamic> jsonCopy(Map<String, dynamic> original) {
      Map<String, dynamic> copy = {};
    
      original.forEach((key, value) {
        if (value is Map<String, dynamic>) {
          copy[key] = jsonCopy(value); // Recursively deep copy nested maps
        } else if (value is List) {
          copy[key] = _listCopy(value); // Call listCopy for lists
        } else if (value is String ||
                   value is num ||
                   value is bool ||
                   value == null) {
          copy[key] = value; // Only accept JSON data types: string, number, boolean, null
        } else {
          throw ArgumentError('Unsupported data type found in JSON object: $value');
        }
      });
    
      return copy;
    }
    
    List<dynamic> _listCopy(List<dynamic> original) {
      List<dynamic> copy = [];
    
      for (var item in original) {
        if (item is Map<String, dynamic>) {
          copy.add(jsonCopy(item)); // Recursively deep copy nested maps
        } else if (item is List) {
          copy.add(_listCopy(item)); // Recursively deep copy nested lists
        } else if (item is String ||
                   item is num ||
                   item is bool ||
                   item == null) {
          copy.add(item); // Only accept JSON data types: string, number, boolean, null
        } else {
          throw ArgumentError('Unsupported data type found in JSON array: $item');
        }
      }
    
      return copy;
    }
    

    To Json => from Json

    An alternative could be just convert you JSON object to a string and then back to an object:

    try {
      final Map<String, dynamic> copy = json.decode(json.encode(original));
    } catch (e) {
      throw ArgumentError('Couldn't parse object data to JSON');
    }