LCOV - code coverage report
Current view: top level - lib - json_object_lite.dart (source / functions) Hit Total Coverage
Test: coverage.lcov Lines: 105 105 100.0 %
Date: 2017-10-16 Functions: 0 0 -

          Line data    Source code
       1             : /*
       2             :  * Package : JsonObjectLite
       3             :  * Author : S. Hamblett <steve.hamblett@linux.com>
       4             :  * Date   : 22/09/2017
       5             :  * Copyright :  S.Hamblett
       6             :  * Based on json_object (C) 2013 Chris Buckett (chrisbuckett@gmail.com)
       7             :  */
       8             : 
       9             : library json_object_lite;
      10             : 
      11             : import "dart:convert";
      12             : 
      13             : /// Set to true to as required
      14             : bool enableJsonObjectLiteDebugMessages = false;
      15             : 
      16             : /// Debug logger
      17             : void _log(String obj) {
      18             :   if (enableJsonObjectLiteDebugMessages) {
      19           1 :     print(obj);
      20             :   }
      21             : }
      22             : 
      23             : /// JsonObjectLite allows .property name access to JSON by using
      24             : /// noSuchMethod. The object is set to not immutable so properties can be
      25             : /// added.
      26             : @proxy
      27             : class JsonObjectLite<E> extends Object implements Map, Iterable {
      28             :   /// Default constructor.
      29             :   /// Creates a new empty map.
      30           1 :   JsonObjectLite() {
      31           2 :     _objectData = new Map();
      32           1 :     isImmutable = false;
      33             :   }
      34             : 
      35             :   /// Eager constructor parses [jsonString] using [JsonDecoder].
      36             :   ///
      37             :   /// If [t] is given, will replace [t]'s contents from the string and return [t].
      38             :   ///
      39             :   /// If [recursive] is true, replaces all maps recursively with JsonObjects.
      40             :   /// The default value is [true].
      41             :   /// The object is set to immutable, the user must reset this to add more properties.
      42             :   factory JsonObjectLite.fromJsonString(String jsonString,
      43             :       [JsonObjectLite t, bool recursive = true]) {
      44             :     if (t == null) {
      45           1 :       t = new JsonObjectLite();
      46             :     }
      47           3 :     t._objectData = decoder.convert(jsonString);
      48             :     if (recursive) {
      49           2 :       t._extractElements(t._objectData);
      50             :     }
      51           1 :     t.isImmutable = true;
      52             :     return t;
      53             :   }
      54             : 
      55             :   /// An alternate constructor, allows creating directly from a map
      56             :   /// rather than a json string.
      57             :   ///
      58             :   /// If [recursive] is true, all values of the map will be converted
      59             :   /// to [JsonObjectLite]s as well. The default value is [true].
      60             :   /// The object is set to immutable, the user must reset this to add more properties.
      61           1 :   JsonObjectLite.fromMap(Map map, [bool recursive = true]) {
      62           1 :     _objectData = map;
      63             :     if (recursive) {
      64           2 :       _extractElements(_objectData);
      65             :     }
      66           1 :     isImmutable = true;
      67             :   }
      68             : 
      69             :   /// Typed JsonObjectLite
      70             :   static JsonObjectLite toTypedJsonObjectLite(
      71             :       JsonObjectLite src, JsonObjectLite dest) {
      72           2 :     dest._objectData = src._objectData;
      73           1 :     if (src.isImmutable) {
      74           1 :       dest.isImmutable = true;
      75             :     }
      76             :     return dest;
      77             :   }
      78             : 
      79             :   /// Contains either a [List] or [Map]
      80             :   dynamic _objectData;
      81             : 
      82             :   static JsonEncoder encoder = new JsonEncoder();
      83             :   static JsonDecoder decoder = new JsonDecoder(null);
      84             : 
      85             :   /// isImmutable indicates if a new item can be added to the internal
      86             :   /// map via the noSuchMethod property, or the functions inherited from the
      87             :   /// map interface.
      88             :   ///
      89             :   /// If set to true, then only the properties that were
      90             :   /// in the original map or json string passed in can be used.
      91             :   ///
      92             :   /// If set to false, then calling o.blah="123" will create a new blah property
      93             :   /// if it didn't already exist.
      94             :   ///
      95             :   /// Set to true by default when a JsonObjectLite is created with [JsonObjectLite.fromJsonString()]
      96             :   /// or [JsonObjectLite.fromMap()].
      97             :   /// The default constructor [JsonObjectLite()], sets this value to
      98             :   /// false so properties can be added.
      99           1 :   set isImmutable(bool state) => isExtendable = !state;
     100             : 
     101           1 :   bool get isImmutable => !isExtendable;
     102             : 
     103             :   @deprecated
     104             : 
     105             :   /// For compatibility the isExtendable boolean is preserved, however new usage
     106             :   /// should use isImmutable above. Usage is as per JsonObject.
     107             :   bool isExtendable;
     108             : 
     109             :   /// Returns a string representation of the underlying object data
     110             :   String toString() {
     111           3 :     return encoder.convert(_objectData);
     112             :   }
     113             : 
     114             :   /// Returns either the underlying parsed data as an iterable list (if the
     115             :   /// underlying data contains a list), or returns the map.values (if the
     116             :   /// underlying data contains a map).
     117             :   Iterable toIterable() {
     118           2 :     if (_objectData is Iterable) {
     119           1 :       return _objectData;
     120             :     }
     121           2 :     return _objectData.values;
     122             :   }
     123             : 
     124             :   /// noSuchMethod()
     125             :   /// If we try to access a property using dot notation (eg: o.wibble ), then
     126             :   /// noSuchMethod will be invoked, and identify the getter or setter name.
     127             :   /// It then looks up in the map contained in _objectData (represented using
     128             :   /// this (as this class implements [Map], and forwards it's calls to that
     129             :   /// class.
     130             :   /// If it finds the getter or setter then it either updates the value, or
     131             :   /// replaces the value.
     132             :   ///
     133             :   /// If isImmutable = true, then it will disallow the property access
     134             :   /// even if the property doesn't yet exist.
     135             :   dynamic noSuchMethod(Invocation mirror) {
     136             :     int positionalArgs = 0;
     137           1 :     if (mirror.positionalArguments != null)
     138           2 :       positionalArgs = mirror.positionalArguments.length;
     139             :     String property = "Not Found";
     140             : 
     141           2 :     if (mirror.isGetter && (positionalArgs == 0)) {
     142             :       // Synthetic getter
     143           2 :       property = _symbolToString(mirror.memberName);
     144           1 :       if (this.containsKey(property)) {
     145           1 :         return this[property];
     146             :       }
     147           2 :     } else if (mirror.isSetter && positionalArgs == 1) {
     148             :       // Synthetic setter
     149             :       // If the property doesn't exist, it will only be added
     150             :       // if isImmutable = false
     151           2 :       property = _symbolToString(mirror.memberName, true);
     152           1 :       if (!isImmutable) {
     153           3 :         this[property] = mirror.positionalArguments[0];
     154             :       }
     155           1 :       return this[property];
     156             :     }
     157             : 
     158             :     // If we get here, then we've not found it - throw.
     159           2 :     _log("noSuchMethod:: Not found: ${property}");
     160           3 :     _log("noSuchMethod:: IsGetter: ${mirror.isGetter}");
     161           3 :     _log("noSuchMethod:: IsSetter: ${mirror.isGetter}");
     162           3 :     _log("noSuchMethod:: isAccessor: ${mirror.isAccessor}");
     163           1 :     return super.noSuchMethod(mirror);
     164             :   }
     165             : 
     166             :   /// If the object passed in is a MAP, then we iterate through each of
     167             :   /// the values of the map, and if any value is a map, then we create a new
     168             :   /// [JsonObjectLite] replacing that map in the original data with that [JsonObjectLite]
     169             :   /// to a new [JsonObjectLite].  If the value is a Collection, then we call this
     170             :   /// function recursively.
     171             :   ///
     172             :   /// If the object passed in is a Collection, then we iterate through
     173             :   /// each item.  If that item is a map, then we replace the item with a
     174             :   /// [JsonObjectLite] created from the map.  If the item is a Collection, then we
     175             :   /// call this function recursively.
     176             :   ///
     177             :   void _extractElements(data) {
     178           1 :     if (data is Map) {
     179             :       // Iterate through each of the k,v pairs, replacing maps with jsonObjects
     180           1 :       data.forEach((key, value) {
     181           1 :         if (value is Map) {
     182             :           // Replace the existing Map with a JsonObject
     183           2 :           data[key] = new JsonObjectLite.fromMap(value);
     184           1 :         } else if (value is List) {
     185             :           // Recurse
     186           1 :           _extractElements(value);
     187             :         }
     188             :       });
     189           1 :     } else if (data is List) {
     190             :       // Iterate through each of the items
     191             :       // If any of them is a list, check to see if it contains a map
     192             : 
     193           3 :       for (int i = 0; i < data.length; i++) {
     194             :         // Use the for loop so that we can index the item to replace it if req'd
     195           1 :         final listItem = data[i];
     196           1 :         if (listItem is List) {
     197             :           // Recurse
     198           1 :           _extractElements(listItem);
     199           1 :         } else if (listItem is Map) {
     200             :           // Replace the existing Map with a JsonObject
     201           2 :           data[i] = new JsonObjectLite.fromMap(listItem);
     202             :         }
     203             :       }
     204             :     }
     205             :   }
     206             : 
     207             :   /// Convert the incoming method name(symbol) into a string, without using mirrors.
     208             :   String _symbolToString(dynamic value, [bool isSetter = false]) {
     209             :     String ret;
     210           1 :     if (value is Symbol) {
     211             :       // Brittle but we avoid mirrors
     212           1 :       final String name = value.toString();
     213           4 :       ret = name.substring((name.indexOf('"') + 1), name.lastIndexOf('"'));
     214             :       // Setters have an '=' on the end, remove it
     215             :       if (isSetter) {
     216           3 :         ret = ret.replaceFirst("=", "", ret.length - 1);
     217             :       }
     218             :     } else {
     219           1 :       ret = value.toString();
     220             :     }
     221           2 :     _log("_symbolToString:: Method name is: ${ret}");
     222             :     return ret;
     223             :   }
     224             : 
     225             :   ///
     226             :   /// Iterable implementation methods and properties
     227             :   ///
     228             : 
     229           2 :   bool any(bool f(dynamic element)) => this.toIterable().any(f);
     230             : 
     231           2 :   bool contains(dynamic element) => this.toIterable().contains(element);
     232             : 
     233           2 :   E elementAt(int index) => this.toIterable().elementAt(index);
     234             : 
     235           2 :   bool every(bool f(dynamic element)) => this.toIterable().every(f);
     236             : 
     237             :   Iterable<T> expand<T>(dynamic f(dynamic element)) =>
     238           2 :       this.toIterable().expand(f);
     239             : 
     240             :   dynamic firstWhere(bool test(dynamic value), {dynamic orElse}) =>
     241           2 :       this.toIterable().firstWhere(test, orElse: orElse);
     242             : 
     243             :   T fold<T>(T initialValue, T combine(T a, dynamic b)) =>
     244           2 :       this.toIterable().fold(initialValue, combine);
     245             : 
     246           2 :   String join([String separator = ""]) => this.toIterable().join(separator);
     247             : 
     248             :   dynamic lastWhere(bool test(dynamic value), {dynamic orElse}) =>
     249           2 :       this.toIterable().firstWhere(test, orElse: orElse);
     250             : 
     251           2 :   Iterable<T> map<T>(dynamic f(dynamic element)) => this.toIterable().map(f);
     252             : 
     253             :   dynamic reduce(dynamic combine(dynamic value, dynamic element)) =>
     254           2 :       this.toIterable().reduce(combine);
     255             : 
     256             :   dynamic singleWhere(bool test(dynamic value), {dynamic orElse}) =>
     257           2 :       this.toIterable().firstWhere(test, orElse: orElse);
     258             : 
     259           2 :   Iterable<E> skip(int n) => this.toIterable().skip(n);
     260             : 
     261             :   Iterable<E> skipWhile(bool test(dynamic value)) =>
     262           2 :       this.toIterable().skipWhile(test);
     263             : 
     264           2 :   Iterable<E> take(int n) => this.toIterable().take(n);
     265             : 
     266             :   Iterable<E> takeWhile(bool test(dynamic value)) =>
     267           2 :       this.toIterable().takeWhile(test);
     268             : 
     269             :   List<dynamic> toList({bool growable: true}) =>
     270           2 :       this.toIterable().toList(growable: growable);
     271             : 
     272           2 :   Set<dynamic> toSet() => this.toIterable().toSet();
     273             : 
     274           2 :   Iterable<E> where(bool f(dynamic element)) => this.toIterable().where(f);
     275             : 
     276           2 :   E get first => this.toIterable().first;
     277             : 
     278           2 :   Iterator<E> get iterator => this.toIterable().iterator;
     279             : 
     280           2 :   E get last => this.toIterable().last;
     281             : 
     282           2 :   E get single => this.toIterable().single;
     283             : 
     284             :   ///
     285             :   /// Map implementation methods and properties *
     286             :   ///
     287             : 
     288             :   // Pass through to the inner _objectData map.
     289           2 :   bool containsValue(dynamic value) => _objectData.containsValue(value);
     290             : 
     291             :   // Pass through to the inner _objectData map.
     292             :   bool containsKey(dynamic value) {
     293           3 :     return _objectData.containsKey(_symbolToString(value));
     294             :   }
     295             : 
     296             :   // Pass through to the innter _objectData map.
     297           2 :   bool get isNotEmpty => _objectData.isNotEmpty;
     298             : 
     299             :   // Pass through to the inner _objectData map.
     300           2 :   dynamic operator [](dynamic key) => _objectData[key];
     301             : 
     302             :   // Pass through to the inner _objectData map.
     303             :   void forEach(void func(dynamic key, dynamic value)) =>
     304           2 :       _objectData.forEach(func);
     305             : 
     306             :   // Pass through to the inner _objectData map.
     307           2 :   Iterable get keys => _objectData.keys;
     308             : 
     309             :   // Pass through to the inner _objectData map.
     310           2 :   Iterable get values => _objectData.values;
     311             : 
     312             :   // Pass through to the inner _objectData map.
     313           2 :   int get length => _objectData.length;
     314             : 
     315             :   // Pass through to the inner _objectData map.
     316           2 :   bool get isEmpty => _objectData.isEmpty;
     317             : 
     318             :   // Pass through to the inner _objectData map.
     319           2 :   void addAll(dynamic items) => _objectData.addAll(items);
     320             : 
     321             :   /// Specific implementations which check isImmtable to determine if an
     322             :   /// unknown key should be allowed.
     323             :   ///
     324             :   /// If [isImmutable] is false, or the key already exists,
     325             :   /// then allow the edit.
     326             :   /// Throw [JsonObjectLiteException] if we're not allowed to add a new
     327             :   /// key
     328             :   void operator []=(dynamic key, dynamic value) {
     329             :     // If the map is not immutable, or it already contains the key, then
     330           3 :     if (this.isImmutable == false || this.containsKey(key)) {
     331             :       //allow the edit, as we don't care if it's a new key or not
     332           2 :       return _objectData[key] = value;
     333             :     } else {
     334           1 :       throw new JsonObjectLiteException("JsonObject is not extendable");
     335             :     }
     336             :   }
     337             : 
     338             :   /// If [isImmutable] is false, or the key already exists,
     339             :   /// then allow the edit.
     340             :   /// Throw [JsonObjectLiteException] if we're not allowed to add a new
     341             :   /// key
     342             :   void putIfAbsent(dynamic key, ifAbsent()) {
     343           3 :     if (this.isImmutable == false || this.containsKey(key)) {
     344           2 :       return _objectData.putIfAbsent(key, ifAbsent);
     345             :     } else {
     346           1 :       throw new JsonObjectLiteException("JsonObject is not extendable");
     347             :     }
     348             :   }
     349             : 
     350             :   /// If [isImmutable] is false, or the key already exists,
     351             :   /// then allow the removal.
     352             :   /// Throw [JsonObjectLiteException] if we're not allowed to remove a
     353             :   /// key
     354             :   dynamic remove(dynamic key) {
     355           3 :     if (this.isImmutable == false || this.containsKey(key)) {
     356           2 :       return _objectData.remove(key);
     357             :     } else {
     358           1 :       throw new JsonObjectLiteException("JsonObject is not extendable");
     359             :     }
     360             :   }
     361             : 
     362             :   /// If [isImmutable] is false, then allow the map to be cleared
     363             :   /// Throw [JsonObjectLiteException] if we're not allowed to clear.
     364             :   void clear() {
     365           2 :     if (this.isImmutable == false) {
     366           2 :       _objectData.clear();
     367             :     } else {
     368           1 :       throw new JsonObjectLiteException("JsonObject is not extendable");
     369             :     }
     370             :   }
     371             : }
     372             : 
     373             : /// Exception class thrown by JsonObjectLite
     374             : class JsonObjectLiteException implements Exception {
     375           1 :   const JsonObjectLiteException([String message]) : this._message = message;
     376           1 :   String toString() => (this._message != null
     377           1 :       ? "JsonObjectException: $_message"
     378           1 :       : "JsonObjectException");
     379             :   final String _message;
     380             : }

Generated by: LCOV version 1.10