map_list_dot 0.1.2 copy "map_list_dot: ^0.1.2" to clipboard
map_list_dot: ^0.1.2 copied to clipboard

access json or maps & lists with a dot notation for getting or setting data, in code or string interpreter

MapList : quick prototyping #

Create pseudo class with accessors from json descriptor #

A json String, a json 'Dart', or any maps & lists set, are enough to create a class with a dot notation to access properties.

dynamic p1 = MapListMap({
  "name": "Polo",
  "firstName": "marco",
  "birthDate": { "day": 15, "month": 9, "year": 1254 }
});

use getter in dot notation

  print('${p1.firstName} ${p1.name} have now ${DateTime.now().year - p1.birthDate.year} years');
  // -> marco Polo have now 766 years

can apply setters #

p1.firstName = 'Marco';

can dynamically create new named properties #

// add a collection for business cards 
 p1.cards = [];
// add a new card with a pre-filled map 
  p1.cards.add({
    "mail": "ma.po.lo@china.com",
  });

Continue freely the creation chain

// add to this -last added- map a new entry 
p1.cards.last.phone = "+99 01 02 03 04 05";
print(p1.cards.last.mail);
print(p1.cards.last.phone);
//@see examples for more code 

At any time the underlying json is updated and available

print(p1.json); 

Access and update your Data Objects with scripts #

What is available in dot notation within Dart is also available by script :

 var scriptLines = [
    'persons=[]',
    '''persons.add({ "name": "Magellan", "firstName": "Fernando",
      "birthDate": { "day": 15,"month": 3,"year": 1480} 
      })
     ''',
    'persons.last.cards = {"mail": "ma.po.lo@china.com"})',
    'persons.last.cards.phone = "+99 01 02 03 04 05"'
  ];

Script executor is under a MapList responsibility

 dynamic myKB = MapListMap();
  for (String line in scriptLines) myKB.eval(line);

Resulting data can be accessed by code or by script

print( myKB.persons.last.cards.phone);          // code
print(myKB.eval('persons.last.cards.phone'));   // interpreter

now some details #

constructors #

There is two kinds of structures : MapListMap and MapListList
If you decide by yourself, choose your root within this two options:

dynamic myRootMap  = MapListMap();  // empty map { } 
dynamic myRootList = MapListList(); // empty list [ ]

If you rely on a variable json , you can leave a higher factory do the choice :

dynamic myRoot = Maplist(someJson); // return a MapListMap or a MapListList

constructors with json data #

Each constructor can be default as above, or can be initialised with :

  • a Json String
  • an inline maps and lists in dart
  • an already loaded 'Dart Json'
dynamic myRootMap  = MapListMap('{"name":"Polo"}'); // string 
dynamic myRootMap  = MapListMap({"name":"Polo"});   // inline

var myJson = json.decode('{"name":"Polo"}');  
dynamic myRootMap  = MapListMap(myJson);          // already json dart

Same options are for MapListList constructors, but beginning by a List [ ]
Same options with the Factory MapList which will decide of the following.

accessing data #

Classical and dot notation are usable in code and in scripts.
The result is the last leaf which could be a simple data, a List, a Map or a null if not found.

classical notation

root["show"]["videos"][1]["name"]

dot notation

root.show.videos[1].name

General access

a .someName indicates a key entry in a map.

  • if the result is another Map
    • can continue with another key : .someName.someList
  • if the result is a List
    • can continue with an index : someList[1]
    • can use the keyword last : someList.last
      • The result of the result can be a Map or another List
        • someList [10].anotherKey
        • somelist [10] [2]
  • if the result is a simple data, cannot continue notation : must be the last leaf.
    • the full name allows to get data : someName.someList[1] // returns an int;
    • the full name allows to set data : someName.someList[1] = 12;

special words #

Some words are identified as questions or actions.

// on Lists
root.show.videos.length 
root.show.videos.clear()
root.show.videos.isEmpty  
root.show.videos.isNotEmpty 
root.show.videos.last
// on Maps
root.show.length
root.show.clear()
root.show.isEmpty   
root.show.isNotEmpty   

create and set data with dot notation #

create empty structures
dynamic squad = MapListMap(); // create an empty map as squad. 
squad.members = [];        // add an empty list named 'members' 
squad.activities = {};     // add also an empty map 'activities' at first level
dynamic squad = MapListMap({members: [], activities: {}); // the same in one line at construction

Note : With dot notation, create unknown data one level at a time (or use json).

create with more pre-filled data
// creation with direct structure  
 root = MapListMap({"dico":{"hello":{"US": "Hi", "FR": "bonjour"} }});
// If you plan to use heterogeneous data : better to precise type: 
 root = MapListMap( {"dico":<String,dynamic>{"hello":{"US": "Hi", "FR": "bonjour"} }});
  //  or use json string message that do the job with its internal types:
  root = MapListMap(' {"dico":{"hello":{"US": "Hi", "FR": "bonjour"} }} ');
  

can use relay to simplify access

// follow previous sample : create with more complex data 
root.dico.numbers = {"US": ["zero","one","two","three","four","five","sic","seven","eight","nine"]};
var numbers = root.dico.numbers; 
var USnumbers = numbers.US;
print(USnumbers[3]);

Note : except for the root which must be declared dynamic, you can leave var at lower levels as the returned class is a MapList.

add a Map into another Map (both MapListMap)

dynamic car = MapListMap();  // use dynamic to allow dot notation on root
car.brand = "Ford";
car.colors = ["blue","black","white"];
car.chosenColor = "white";
// now add this car to a larger set 
dynamic myStuff = MapListMap();
myStuff.myCar = car;      // create a property myCar with given values 

Add to a List : one element with add, another List with addAll

dynamic list = MapListList();  
list.add(15);
list.addAll([1, 2, 3]); 
list.add({"date":"october 16"}); 
print(list); //[15, 1, 2, 3, {date: october 16}]
add or change elements of a map with another map : addAll
dynamic car = MapListMap();
car.name = "ford";
// add to this map several key:value in one shot or change existing
car.addAll({ "name":"Ford","price": 5000, "fuel":"diesel","hybrid":false});

Check nullable while accessing data #

store.wrongName.someData

To avoid the error "NoSuchMethodError: The getter 'someData' was called on null",
Dart takes care of the nullable notation.
The following code will return null or the data, without throwing error.

store.wrongName?.someData
store.eval('wrongName?.someData');

note: The interpreter takes care of the null notation.

MapLists return null on unknown data #

  • unknown key in a Map
  • wrong index on a List
  • misused of types , like indexing a Map or using key on a List
  • for interpreter :
    • wrong syntax
    • malformed json

In all cases, MapList will returns null and logs a Warning on the standard logger.

Wrong index on a List : sample of log

MapList logs a Warning with a reminder of the initial error :

print(store.book[400]);  // -> null 
    // WARNING: unexisting book [400] : null returned .
    // Original message : RangeError (index): Invalid value: Not in range 0..3, inclusive: 4 

You can protect downstream errors with a nullable option :

store.book[400]?.author = "zaza" ;  

Non existing List

If the list doesn't exist at all, the nullable must be checked before the index to avoid error on the operator [ ]:

store.pocketBookList?[0].author = "zaza";

Dart allows this syntax recently with Dart 2.9.2.
Before 2.9.2 you cannot compile with a nullable before [0] in code.
The interpreter already allows this syntax.

The hell of data types and how to protect code #

MapList works on a basis of Map<String,dynamic> and List<dynamic> .
Using and adding json data, which are Map<dynamic,dynamic> and List<dynamic>, is full compliant.

warn with inline coded structure

This codes with a List will fail:

root.data = [11, 12, 13];   // will infer a List<int> 
root.data.add(14);          // ok
root.data.add("hello");     // will fail :type 'String' is not a subtype of type 'int'

If a type is not indicated, Dart infers the type from the current assignment and [11,12,13] will be a List<int>.
From there, you can only add other <int> without errors, nothing else like "hello" without a crash.
Similar things can happen with a map.
This code will fail :

root.results = {"elapsed_time": 30, "temperature": 18} // is ok but result is a List of Map<String, int>  
root.results.time = "12:58:00";        // will fail : type 'String' ("12:58:00") is not a subtype of type 'int' of 'value'

add <dynamic>

Think about adding type

root.data = <dynamic> [11, 12, 13];  
root.data.add(14);          // ok
root.data.add("hello");     // ok
root.results =  <String,dynamic>{"elapsed_time": 60, "temperature": 40};
root.results.time = "12:58:00"; // now ok ! 

If you use constructors with a String structure, or 'dart json', MapList do the job of enlarging types to dynamic.

Logged errors #

MapList try to avoid runtime errors:
If something is wrong, it logs Warning but continue without errors:

  • On a wrong get, it logs message and returns null .
  • On a wrong set : it logs message and do nothing else .
    (To see the messages, you must set a logging listener in your code (@see standard logging package).)

common warning : using List as Map or Map as List

 aList["toto"]="riri";
**WARNING** index ["toto"] must be applied to a map. no action done.
aMap[0]="lulu": 
**WARNING**  [0] must be applied to a List. no action done.
print(aList.price);      
**WARNING** Naming error: trying to get a key "price" in a List. null returned    

Wrong index in List

print(root[200]);       
**WARNING**:  unknown accessor: . [200] : null returned .(followed by original dart error 'Not in range..') 

Wrong json data in a String at runtime (if direct inline code, compiler will warn )

dynamic root = MapList('{"this": is not a valid entry }'); 
**WARNING** wrong json data. null returned .
(followed by original conversion error) 
remaining runtime that can throw errors

Mainly Type mismatch if inline data are not correctly casted .
Forgotten nullable in the evaluated path.
Leaving dart inline tolerance in script or json : comma at the end [11,12,]

some words about Yaml

I do prefer coding in yaml rather in json, but this have some defaults :

var yamlStructure = loadYaml(yamlString);
dynamic root = MapList(yamlStructure);
print(root.show.name);  // ok 
root.show.name = "new video title";
-> 'runtime Error: Unsupported operation: Cannot modify an unmodifiable Map';

If all get can work, no set are allowed because the standards yamlMap and yamlList are read only.

tips for Yaml

The most simple way to transform a read-only yaml in a full compliant json is the following :

root = MapList(json.decode(json.encode(yamlStructure)));

Some More details #

A MapList has a .json accessor to get the wrapped one if necessary.
MapList works with pointers, not copies :
json data stay synchronised between :

  • direct access to json
  • use with MapList in code
  • or use of MapList interpreter.

Embed MapList in a class with specific methods #

As is, MapListMap and MapListList are in the category of DataObjects
You can mix free dynamic set of data and classical methods within a class that extends a MapList flavour.
Such a class is in an example with the following class Person :

class Person extends MapListMap{
  Person(some):super(some);
 }

using dot notation inside the class : keyword me. #

As properties are free and in a json , a method cannot use this.someProperty as someProperty is not declared in class.
To allow retrieval of properties with dot notation, just use the keyword me. ( which is a cast of this as dynamic ).
In examples, we define an internal getter to the class Person, using dynamic data with me. :

int get age {
    return (DateTime.now().year - me.birthDate.year);
  }


MapList access interpreter #

An interpreter have some well known use cases :

  • applying create or update on a data set from textual messages
  • free interaction with data not known at compile time (knowledge base, blackboard pattern,...)
  • using maps and lists as an open graph

underlying base : JsonNode #

You don't really need to use JsonNode as such, but MapList uses it to walk the graph.
JsonNode is a kind of canoe that navigates on the data graph with :

  • fromNode
  • edge
  • toNode
  • (ascript : the path or the remaining path)

When you create a JsonNode with a path, it returns the last step of its journey.
To view itn the toString returns (type) fromNode -- last edge in use---> (type) toNode
(As a node could be a large thing, toString returns the beginning... of the data )

returning a data leaf

Below is shown the internal structure to see internal data.

var aJson = [ [1, 2, 3], [11, 12],  {"A": "AA", "B": "BB"},  "andSoOn" ];
print(JsonNode(aJson, '[0][1]')); // (list)[1, 2, 3]  --- 1 -> (int) 2
print(JsonNode(aJson, '[2]["B"]')); // (map){A: AA, B: BB}  --- B -> (String) BB  
print(JsonNode(aJson, '[2].length')); // (map){A: AA, B: BB}  --- length -> (int) 2  

returning a tree branch

var aJson = [ [1, 2, 3], [11, 12],  {"A": "AA", "B": "BB"},  "andSoOn" ];
print(JsonNode(aJson,'[0]')); // (list)[[1, 2, 3], [11...  --- 0 -> (list) [1, 2, 3]  
print(JsonNode(aJson,'[2]')); //(list)[[1, 2, 3], [11...  --- 2 -> (map) {A: AA, B: BB} 

If you plan to use directly JsonNode, you can get the data by .value
(which is a convenient name to get the last toNode )
assert(JsonNode(aJson, '[2].B').value == "BB");

special words

JsonNode recognize some keywords:

  • .length
  • .isEmpty
  • .isNotEmpty
  • .last (on Lists )
  • .clear()

Note about length
Always use .length to get the length of a List or a Map. If there is a key length in a map, you can reach it with notation ["length"]

   dynamic store = MapList('{"bikes":[{"name":"Fonlupt", "length":2.1, "color" : "green" }]}');
   assert(store.eval('bikes[0].length') == 3);
   assert(store.eval('bikes[0]["length"]')== 2.1);

how a caller can create unknown new data

When a path ends with an unknown name in a map, the last node is null but not the trip :

   print(JsonNode(aJson, 'questions.newData'));// (map){A: AA, B: BB}  --- newData -> (Null) null 

A caller, like MapList do, can check the results and set the data with fromNode[edge] .
(if the path starts at the very beginning, the fromNode is also null and the edge must be applied to the root )

Same Hell of types in interpreter

Same precautions has to be taken on datatypes to avoid bad surprises.
Writing inline data with types could be cumbersome :

var aJson = <dynamic>[ [1, 2, 3], <String,dynamic> {"A": "AA", "B": "BB"},  "andSoOn" ];  

Tip: Prefer using a json String and let MapList do a json.decode(string) do the job.

  • type in your inline structure to see its correctness : [ [1, 2, 3], {"A": "AA", "B": "BB"}, "andSoOn" ]
  • wrap it in quotes : '[ [1, 2, 3], {"A": "AA", "B": "BB"}, "andSoOn" ]'
  • use it in MapList(someString)

MapList eval(script) method : get and set data by script #

MapList uses underline the previous JsonNode mechanism, get the value and allows to create and set data.

get data

You can use same notations than in code to access a data, classical or dot notation.
MapList will return directly the data:

  • for an end leaf, it returns the value,
  • for an intermediary node, it returns the json wrapped in a new MapList (returning a MapList allows to combine interpreter and direct code with dot notation in code.)

Notice that the root is the executor and is not repeated in the path :
The script below returns a simple data :

    dynamic book = MapList('{ "name":"zaza", "friends": [{"name": "lulu" }]}');
    print(book.eval('friends[0].name')); // -->  lulu);

All notation styles are allowed :
root.eval('["show"]["videos"][1]["questions"][1]["name"]') // classical notation
root.eval('show.videos[1].questions[1].options[2].answer') // dot notation
Special word returns also direct values:

if (store.eval('store.bikes.isEmpty')) print ('what a disaster');

scripts to set data #

assignment with equal symbol

MapList interpreter takes care of an assignment in the script.
The Left Hand Side of a script with assignment is the path to get by MapList.
The Right Hand Side is evaluated as a simple type data or as a json structure.

    dynamic squad = MapList();          // will create a default Map
    squad.eval('name = "Super hero squad"');    // add a String data
    squad.eval('homeTown = "Metro City"');      // another
    squad.eval('formed = 2016');                // add an int
    squad.eval('active = true');                // add a bool
    squad.eval('score = 38.5');                 // add a double
    squad.eval('overhauls = ["2008/04/10", "2102/05/01", "2016/04/17"]'); // add structured data 

special functions to create or update data

  • add(something)
    • Only for Lists : add a new element
  • addAll(several something)
    • add all elements of a map to a map
    • all elements of a list to a list.
  • remove(something)
    • Remove an entry of a map
    • Remove an element in a list
  • length = <int>
    • force the length of a List

errors and logs #

See the previous chapter on errors and logs for common access errors.
Some errors normally detected by compiler can happen in interpreted string.
As an example below is a missing parenthesis around functions.

root.eval('clear'); // WARNING cannot search a key (clear) in a List<dynamic> 
root.eval('clear()'); // ok 

Nullable capacities #

Interpreter takes care of nullable notations at all levels :

store.eval('wrongName?.someData')
store.eval("book[4]?.author
store.eval("bookList?[0]")   // Even if Dart is not in 9.2, the interpreter allows this nullable.    
store.eval("bookList?[0]?.date")

Weakness

Probably some in the analysis of syntax in interpreter : in case of trouble verify deeply your strings.
MapList uses Symbol without mirrors and get the symbol name by hand : This could have issues with dart.js minifier, this has not been tested here.

2
likes
30
pub points
26%
popularity

Publisher

verified publisherpep-inno.com

access json or maps & lists with a dot notation for getting or setting data, in code or string interpreter

Repository (GitHub)
View/report issues

License

BSD-3-Clause (LICENSE)

Dependencies

logging

More

Packages that depend on map_list_dot