Jelajahi Sumber

Basic working Json tools, structure tidy-up

Billy Barrow 10 bulan lalu
induk
melakukan
7c51c8b354

+ 16 - 15
src/json/JsonArray.vala → src/json/Array.vala

@@ -1,55 +1,56 @@
+using Invercargill;
 
 namespace InvercargillJson {
 
-    public class JsonArray : Invercargill.IndexedCollection<Json.Node>, JsonNodes, JsonGLibObjectWrapper<Json.Array> {
+    public class JsonArray : IndexedCollection<JsonElement>, Elements, JsonElements {
 
         private Json.Array array;
 
-        public Json.Array json_glib_object { get { return array; } }
+        internal Json.Array json_glib_object { get { return array; } }
 
         public JsonArray() {
             array = new Json.Array();
         }
 
-        public JsonArray.from_existing(Json.Array array) {
+        internal JsonArray.from_existing(Json.Array array) {
             this.array = array;
         }
 
-        public override Invercargill.Tracker<Json.Node> get_tracker() {
+        public override Tracker<Element> get_tracker() {
             var i = 0;
-            return new Invercargill.LambdaTracker<Json.Node>(
+            return new LambdaTracker<Element>(
                 () => {
                     return i < array.get_length();
                 },
                 () => {
-                    return array.get_element(i++);
+                    return new JsonElement.from_node(array.get_element(i++));
                 });
         }
 
-        public new override Json.Node @get(int index) {
-            return array.get_element(index);
+        public new override JsonElement @get(int index) {
+            return new JsonElement.from_node(array.get_element(index));
         }
-        public override int index_of(Invercargill.PredicateDelegate<Json.Node> predicate) {
+        public override int index_of(PredicateDelegate<Element> predicate) {
             return with_positions().first_or_default(i => predicate(i.item))?.position ?? -1;
         }
         public override void remove(int index) {
             array.remove_element(index);
         }
-        public new override void @set(int index, Json.Node item) {
+        public new override void @set(int index, JsonElement item) {
             assert_not_reached();
         }
-        public override void add(Json.Node item) {
-            array.add_element(item);
+        public override void add(JsonElement item) {
+            array.add_element(item.assert_as<Json.Node>());
         }
-        public override void remove_first_where(Invercargill.PredicateDelegate<Json.Node> predicate) {
+        public override void remove_first_where(Invercargill.PredicateDelegate<JsonElement> predicate) {
             var index = index_of(predicate);
             if(index >= 0) {
                 array.remove_element(index);
             }
         }
-        public override void remove_where(Invercargill.PredicateDelegate<Json.Node> predicate) {
+        public override void remove_where(Invercargill.PredicateDelegate<JsonElement> predicate) {
             with_positions()
-                .where(i => predicate(i.item))
+                .where(i => predicate((JsonElement)i.item))
                 .iterate(i => array.remove_element(i.position));
         }
     }

+ 54 - 0
src/json/Elements.vala

@@ -0,0 +1,54 @@
+
+using Invercargill;
+namespace InvercargillJson {
+
+    public interface JsonElements : Enumerable<JsonElement>, Elements {
+
+        public virtual Enumerable<JsonArray> as_arrays() {
+            return select<JsonArray>(n => new JsonArray.from_existing(n.assert_as<Json.Node>().get_array()));
+        }
+
+        public virtual Enumerable<bool> as_bools() {
+            return select<bool>(n => n.assert_as<Json.Node>().get_boolean());
+        }
+
+        public virtual Doubles as_doubles() {
+            return select<double?>(n => n.assert_as<Json.Node>().get_double()).promote_to<Doubles>();
+        }
+
+        public virtual Signed64BitIntegers as_integers() {
+            return select<int64?>(n => n.assert_as<Json.Node>().get_int()).promote_to<Signed64BitIntegers>();
+        }
+
+        public virtual Enumerable<string> as_strings() {
+            return select<string>(n => n.assert_as<Json.Node>().get_string());
+        }
+
+        public virtual Enumerable<JsonObject> as_objects() {
+            return select<JsonObject>(n => new JsonObject.from_existing(n.assert_as<Json.Node>().get_object()));
+        }
+
+        public virtual JsonArray to_json_array() {
+            var array = new JsonArray();
+            array.add_all((Enumerable<JsonElement>)this);
+            return array;
+        }
+
+    }
+
+    private class JsonElementsPromotionImplementation : Object, Promotion<Element> {
+        public bool can_wrap(GLib.Type element_type) {
+            return element_type.is_a(typeof(JsonElement));
+        }
+        public Enumerable<Element> wrap(Enumerable<Element> enumerable) {
+            return new JsonElementsProxy(enumerable.cast<JsonElement>());
+        }
+    }
+
+    private class JsonElementsProxy : ProxyEnumerable<JsonElement>, Elements, JsonElements {
+        public JsonElementsProxy(Enumerable<JsonElement> elements) {
+            inner = elements;
+        }
+    }
+
+}

+ 245 - 10
src/json/Json.vala

@@ -2,43 +2,278 @@
 namespace InvercargillJson {
 
 
-    public interface JsonGLibObjectWrapper<T> {
-        public abstract T json_glib_object { get; }
-    }
-
-    public class JsonDatum : Object, Invercargill.Element {
+    public class JsonElement : Object, Invercargill.Element {
 
         private Json.Node node;
 
+        public JsonElement.from_string(string json) throws GLib.Error {
+            node = Json.from_string (json);
+        }
+
+        internal JsonElement.from_node(Json.Node node) {
+            this.node = node;
+        }
+
+        public JsonElement.from_element(Invercargill.Element element) throws Invercargill.ElementError {
+            // Null node is top priority
+            if(element.is_null ()) {
+                node = new Json.Node(Json.NodeType.NULL);
+                return;
+            }
+
+            // Second priority is creation of arrays and objects
+            if(element.assignable_to<Invercargill.Properties>()) {
+                node = new Json.Node (Json.NodeType.OBJECT);
+                var object = new JsonObject();
+                foreach (var item in element.as<Invercargill.Properties>()) {
+                    object.set(item.key, new JsonElement.from_element(item.value));
+                }
+                node.set_object (object.json_glib_object);
+                return;
+            }
+            if(element.assignable_to<Invercargill.Elements>()) {
+                node = new Json.Node (Json.NodeType.ARRAY);
+                var array = new JsonArray();
+                foreach (var item in element.as<Invercargill.Elements>()) {
+                    array.add(new JsonElement.from_element (item));
+                }
+                node.set_array (array.json_glib_object);
+                return;
+            }
+
+            node = new Json.Node (Json.NodeType.VALUE);
+            var type = element.type();
+
+            // Third priority is conversion of known types
+            if(type == typeof(DateTime)) {
+                node.set_string (element.as<DateTime>().format_iso8601());
+                return;
+            }
+            if(type == typeof(string)) {
+                node.set_string (element.as<string>());
+                return;
+            }
+            if(type == typeof(bool)) {
+                node.set_string (element.as<string>());
+                return;
+            }
+            if(type == typeof(double)) {
+                node.set_string (element.as<string>());
+                return;
+            }
+            if(type == typeof(int64)) {
+                node.set_string (element.as<string>());
+                return;
+            }
+            if(type == typeof(bool)) {
+                node.set_string (element.as<string>());
+                return;
+            }
+            if(type == typeof(uint8)) {
+                node.set_int (element.as<uint>());
+                return;
+            }
+            if(type == typeof(int8)) {
+                node.set_int (element.as<int>());
+                return;
+            }
+            if(type == typeof(uint16)) {
+                node.set_int (element.as<uint16>());
+                return;
+            }
+            if(type == typeof(int16)) {
+                node.set_int (element.as<int16>());
+                return;
+            }
+            if(type == typeof(uint32)) {
+                node.set_int (element.as<uint32>());
+                return;
+            }
+            if(type == typeof(int32)) {
+                node.set_int (element.as<int32>());
+                return;
+            }
+            if(type == typeof(uint64?)) {
+                node.set_int ((int64)element.as<uint64?>());
+                return;
+            }
+            if(type == typeof(float?)) {
+                node.set_double (element.as<float?>());
+                return;
+            }            
+
+            // Last priority is the element's native conversions
+            if(element.assignable_to<string>()) {
+                node.set_string (element.as<string>());
+                return;
+            }
+            if(element.assignable_to<bool> ()) {
+                node.set_boolean(element.as<bool>());
+                return;
+            }
+            if(element.assignable_to<double?>()) {
+                node.set_double(element.as<double?>());
+                return;
+            }
+            if(element.assignable_to<int64?>()) {
+                node.set_int(element.as<int64?>());
+                return;
+            }
+
+            // Could not convert
+            throw new Invercargill.ElementError.INVALID_CONVERSION(@"No way to convert element $(element.get_type ().name()) containing type $(type.name()) to a type suitable for a JsonElement.");
+        }
+
         public bool assignable_to_type (GLib.Type type) {
             if(node.get_node_type() == Json.NodeType.ARRAY) {
-                return type.is_a (typeof(JsonArray)) || type.is_a (typeof(Json.Array));
+                return
+                    type == typeof(JsonArray) || 
+                    type == typeof(JsonElements) || 
+                    type == typeof(Invercargill.Elements) ||
+                    type == typeof(Json.Array);
             }
-            if(node.get_node_type() == Json.NodeType.ARRAY) {
-                return type.is_a (typeof(JsonArray)) || type.is_a (typeof(Json.Array));
+            if(node.get_node_type() == Json.NodeType.OBJECT) {
+                return
+                    type == typeof(JsonObject) || 
+                    type == typeof(Invercargill.Properties) ||
+                    type == typeof(Json.Object);
             }
-            if(node.get_value_type().is_a(type)) {
+            if(node.is_null() || type == typeof(Json.Node)) {
                 return true;
             }
+            if(node.get_value_type().is_a(typeof(int64)) || node.get_value_type ().is_a(typeof(double))) {
+                return
+                    type == typeof(uint8) ||
+                    type == typeof(int8) ||
+                    type == typeof(uint16) ||
+                    type == typeof(int16) ||
+                    type == typeof(uint32) ||
+                    type == typeof(int32) ||
+                    type == typeof(uint64) ||
+                    type == typeof(int64) ||
+                    type == typeof(double) ||
+                    type == typeof(float) ||
+                    type == typeof(string);
+            }
+            if(node.get_value_type().is_a(typeof(string))) {
+                return
+                    type == typeof(string) ||
+                    (type == typeof(DateTime) && new DateTime.from_iso8601(node.get_string(), null) != null);
+            }
+            if(node.get_value_type().is_a(typeof(bool))) {
+                return type == typeof(bool);
+            }
             return false;
         }
         public bool is_null () {
             return node.is_null();
         }
         public bool try_get_as<T> (out T result) {
+            if(!assignable_to<T>()) {
+                result = null;
+                return false;
+            }
+            if(node.is_null ()) {
+                result = null;
+                return true;
+            }
+
             if(typeof(T) == typeof(Json.Node)) {
                 result = node;
                 return true;
             }
+            if(typeof(T) == typeof(JsonArray) || typeof(T) == typeof(Invercargill.Elements)) {
+                result = new JsonArray.from_existing (node.get_array ());
+                return true;
+            }
+            if(typeof(T) == typeof(Json.Array)) {
+                result = node.get_array();
+                return true;
+            }
+            if(typeof(T) == typeof(JsonObject) || typeof(T) == typeof(Invercargill.Properties)) {
+                result = new JsonObject.from_existing (node.get_object());
+                return true;
+            }
+            if(typeof(T) == typeof(Json.Object)) {
+                result = node.get_object();
+                return true;
+            }
+            if(typeof(T).is_a(typeof(uint8))) {
+                result = (uint8)node.get_int();
+                return true;
+            }
+            if(typeof(T).is_a(typeof(int8))) {
+                result = (int8)node.get_int();
+                return true;
+            }
+            if(typeof(T).is_a(typeof(uint16))) {
+                result = (uint16)node.get_int();
+                return true;
+            }
+            if(typeof(T).is_a(typeof(int16))) {
+                result = (int16)node.get_int();
+                return true;
+            }
+            if(typeof(T).is_a(typeof(uint32))) {
+                result = (uint32)node.get_int();
+                return true;
+            }
+            if(typeof(T).is_a(typeof(int32))) {
+                result = (int32)node.get_int();
+                return true;
+            }
+            if(typeof(T).is_a(typeof(uint64))) {
+                uint64? r = node.get_int();
+                result = r;
+                return true;
+            }
+            if(typeof(T).is_a(typeof(int64))) {
+                int64? r = node.get_int();
+                result = r;
+                return true;
+            }
+            if(typeof(T).is_a(typeof(double))) {
+                double? r = node.get_double();
+                result = r;
+                return true;
+            }
+            if(typeof(T).is_a(typeof(float))) {
+                float? r = (float)node.get_double();
+                result = r;
+                return true;
+            }
+            if(typeof(T).is_a(typeof(string))) {
+                if(node.get_value_type().is_a (typeof(double))) {
+                    result = node.get_double().to_string();
+                }
+                else if(node.get_value_type().is_a(typeof(int64))) {
+                    result = node.get_int().to_string();
+                }
+                result = node.get_string();
+                return true;
+            }
+            if(typeof(T).is_a(typeof(DateTime))) {
+                result = new DateTime.from_iso8601(node.get_string (), null);
+                return true;
+            }
+            if(typeof(T).is_a(typeof(bool))) {
+                result = node.get_boolean();
+                return true;
+            }
 
-
+            warning(@"Type \"$(typeof(T).name())\" passed assignable check but the conversion is not implemented");
             result = null;
             return false;
         }
+
         public GLib.Type? type () {
             return typeof(Json.Node);
         }
 
+        public string stringify(bool pretty = false) {
+            return Json.to_string (node, pretty);
+        }
+
     }
 
 }

+ 0 - 50
src/json/JsonNodes.vala

@@ -1,50 +0,0 @@
-
-using Invercargill;
-namespace InvercargillJson {
-
-    public interface JsonNodes : Enumerable<Json.Node> {
-
-        public virtual Enumerable<JsonArray> as_arrays() {
-            return select<JsonArray>(n => new JsonArray.from_existing(n.get_array()));
-        }
-
-        public virtual Enumerable<bool> as_bools() {
-            return select<bool>(n => n.get_boolean());
-        }
-
-        public virtual Doubles as_doubles() {
-            return select<double?>(n => n.get_double()).promote_to<Doubles>();
-        }
-
-        public virtual Signed64BitIntegers as_integers() {
-            return select<int64?>(n => n.get_int()).promote_to<Signed64BitIntegers>();
-        }
-
-        public virtual Enumerable<string> as_strings() {
-            return select<string>(n => n.get_string());
-        }
-
-        public virtual Enumerable<JsonObject> as_objects() {
-            return select<JsonObject>(n => new JsonObject.from_existing(n.get_object()));
-        }
-
-        public virtual JsonArray to_json_array() {
-            var array = new JsonArray();
-            array.add_all(this);
-            return array;
-        }
-
-    }
-
-    private class JsonNodesPromotionImplementation : ProxyEnumerable<Json.Node>, JsonNodes, Promotion<Json.Node> {
-        public bool can_wrap(GLib.Type element_type) {
-            return element_type.is_a(typeof(Json.Node));
-        }
-        public Enumerable<Json.Node> wrap(Enumerable<Json.Node> enumerable) {
-            var wrapped = new JsonNodesPromotionImplementation();
-            wrapped.inner = enumerable;
-            return wrapped;
-        }
-    }
-
-}

+ 8 - 8
src/json/JsonObject.vala → src/json/Object.vala

@@ -2,7 +2,7 @@ using Invercargill;
 
 namespace InvercargillJson {
 
-    public class JsonObject : Associative<string, Json.Node>, JsonGLibObjectWrapper<Json.Object> {
+    public class JsonObject : Associative<string, JsonElement>, Properties {
 
         private Json.Object object;
 
@@ -21,26 +21,26 @@ namespace InvercargillJson {
         public override bool has (string key) {
             return object.has_member(key);
         }
-        public new override void set (string key, Json.Node value) {
-            object.set_member(key, value);
+        public new override void @set (string key, JsonElement value) {
+            object.set_member(key, value.assert_as<Json.Node>());
         }
-        public override bool try_get (string key, out Json.Node value) {
+        public override bool try_get (string key, out JsonElement value) {
             if(has(key)) {
-                value = object.get_member(key);
+                value = new JsonElement.from_node(object.get_member(key));
                 return true;
             }
             value = null;
             return false;
         }
-        public override Tracker<KeyValuePair<string, Json.Node>> get_tracker () {
+        public override Tracker<KeyValuePair<string, Element>> get_tracker () {
             var keys = object.get_members ();
             var i = 0;
-            return new LambdaTracker<KeyValuePair<string, Json.Node>>(
+            return new LambdaTracker<KeyValuePair<string, Element>>(
                 () => {
                     return i < keys.length();
                 },
                 () => {
-                    return new KeyValuePair<string, Json.Node> (keys.nth_data(i), object.get_member(keys.nth_data(i)));
+                    return new KeyValuePair<string, Element> (keys.nth_data(i), new JsonElement.from_node(object.get_member(keys.nth_data(i++))));
                 });
         }
 

+ 2 - 2
src/json/PromotionRegistration.c

@@ -2,9 +2,9 @@
 #include "invercargill-json.h"
 #include "invercargill.h"
 
-VALA_EXTERN GType invercargill_json_json_nodes_promotion_implementation_get_type (void) G_GNUC_CONST ;
+VALA_EXTERN GType invercargill_json_json_elements_promotion_implementation_get_type (void) G_GNUC_CONST ;
 
 __attribute__((constructor))
 static void register_promotions() {
-    invercargill_register_promotion (INVERCARGILL_JSON_TYPE_JSON_NODES, invercargill_json_json_nodes_promotion_implementation_get_type());
+    invercargill_register_promotion (INVERCARGILL_JSON_TYPE_JSON_ELEMENTS, invercargill_json_json_elements_promotion_implementation_get_type());
 }

+ 3 - 3
src/json/meson.build

@@ -9,9 +9,9 @@ dependencies = [
 
 sources = files('Json.vala')
 sources += files('PromotionRegistration.c')
-sources += files('JsonNodes.vala')
-sources += files('JsonArray.vala')
-sources += files('JsonObject.vala')
+sources += files('Elements.vala')
+sources += files('Array.vala')
+sources += files('Object.vala')
 
 invercargill_json = shared_library('invercargill-json', sources,
     dependencies: dependencies,

+ 93 - 0
src/lib/Converter.vala

@@ -0,0 +1,93 @@
+
+//  namespace Invercargill {
+
+//      [GenericAccessors]
+//      public interface Converter<TSource, TDestination> : Object {
+        
+//          public virtual Type source_type() {
+//              return typeof(TSource);
+//          }
+
+//          public virtual Type destination_type() {
+//              return typeof(TDestination);
+//          }
+
+//          public abstract TDestination convert(TSource source);
+
+//      }
+
+//      private static Dictionary<ConverterKey, Converter> converter_registry = null;
+
+//      private static void ensure_converter_registry() {
+//          if(converter_registry == null) {
+//              converter_registry = new Dictionary<ConverterKey, Converter>();
+            
+//          }
+//      }
+
+//      public static void register_converter(Converter converter) {
+//          ensure_converter_registry();
+//          converter_registry.set(new ConverterKey.from_converter(converter), converter);
+//      }
+
+//      public static void register_converter_func<TSource, TDestination>(TransformDelegate<TSource, TDestination> func) {
+//          register_converter(new LambdaConverter<TSource, TDestination>(func));
+//      }
+    
+//      public static Converter? get_converter_for_types(Type source, Type destination) {
+//          ensure_converter_registry();
+//          return converter_registry.get_or_default(new ConverterKey(source, destination));
+//      }
+
+//      public static Converter? get_converter<TSource, TDestination>() {
+//          return get_converter_for_types(typeof(TSource), typeof(TDestination));
+//      }
+
+//      public static TDestination convert<TSource, TDestination>(TSource source) throws ConverterError {
+//          var converter = get_converter<TSource, TDestination>();
+//          if(converter == null) {
+//              throw new ConverterError.CONVERTER_NOT_FOUND(@"No converter registered that can convert from $(typeof(TSource).name()) to $(typeof(TDestination).name())");
+//          }
+//          return ((Converter<TSource, TDestination>)converter).convert(source);
+//      }
+
+//      private class ConverterKey : Hashable, Equatable<ConverterKey> {
+//          public Type source { get; set; }
+//          public Type destination { get; set; }
+
+//          public uint hash_code() {
+//              return @"$(source.name())>$(destination.name())".hash();
+//          }
+//          public bool equals(ConverterKey other) {
+//              return
+//                  source == other.source &&
+//                  destination == other.destination;
+//          }
+
+//          public ConverterKey(Type src, Type dst) {
+//              source = src;
+//              destination = dst;
+//          }
+
+//          public ConverterKey.from_converter(Converter converter) {
+//              source = converter.source_type();
+//              destination = converter.destination_type();
+//          }
+
+//      }
+
+//      private class LambdaConverter<TSource, TDestination> : Object, Converter<TSource, TDestination> {
+
+//          private TransformDelegate<TSource, TDestination> func;
+
+//          public LambdaConverter(TransformDelegate<TSource, TDestination> function) {
+//              func = function;
+//          }
+
+//          public TDestination convert(TSource source) {
+//              return func(source);
+//          }
+
+//      }
+
+//  }

+ 2 - 0
src/lib/Delegates.vala

@@ -2,6 +2,8 @@ namespace Invercargill {
 
     public delegate void ItemDelegate<T>(T item);
 
+    public delegate bool TryTransformDelegate<Tin, Tout>(Tin item, ref Tout? res);
+
     public delegate Tout TransformDelegate<Tin, Tout>(Tin item);
 
     public delegate bool PredicateDelegate<T>(T item);

+ 32 - 13
src/lib/Element.vala

@@ -18,18 +18,20 @@ namespace Invercargill {
     // public void populate(Properties props)
 
 
-    public interface Elements : Enumerable<Element> {}
-    private class ElementSeries : Series<Element>, Elements {}
+    public interface Elements : Enumerable<Element> {
+
+        public virtual Enumerable<T> elements_as<T>() {
+            return try_select<T>((e, ref r) => e.try_get_as<T>(out r));
+        }
+
+        public virtual Enumerable<T> assert_elements_as<T>() {
+            return select<T>(e => e.assert_as<T>());
+        }
 
-    public enum ElementCategory {
-        NULL,
-        BOOL,
-        NUMBER,
-        STRING,
-        ELEMENTS,
-        KEY_VALUES,
-        PROPERTIES
     }
+    
+    private class ElementSeries : Series<Element>, Elements {}
+
 
     public interface Element : Object {
 
@@ -62,6 +64,23 @@ namespace Invercargill {
             throw new ElementError.INVALID_CONVERSION(@"Could not convert from $(type_name()) to $(typeof(T).name()).");
         }
 
+        public virtual T? as_or_default<T>() {
+            T result;
+            if(try_get_as<T>(out result)) {
+                return result;
+            }
+            return null;
+        }
+
+        public virtual T assert_as<T>() {
+            T result;
+            if(try_get_as<T>(out result)) {
+                return result;
+            }
+            critical(@"Element.assert_as<$(typeof(T).name())> failed: Could not get $(this.get_type().name()) as type $(typeof(T).name()).");
+            assert_not_reached();
+        }
+
 
         public virtual string to_string() {
             return @"Element[$(type_name())]";
@@ -82,7 +101,7 @@ namespace Invercargill {
             if(is_type(type)) {
                 return true;
             }
-            if(typeof(T).is_a(typeof(UntypedEnumerable)) && type == typeof(Elements)) {
+            if(typeof(T).is_a(typeof(Enumerable)) && type == typeof(Elements)) {
                 return true;
             }
             return false;
@@ -97,8 +116,8 @@ namespace Invercargill {
         }
 
         public bool try_get_as<TOut>(out TOut result) {
-            if(typeof(T).is_a(typeof(UntypedEnumerable)) && typeof(TOut) == typeof(Elements)) {
-                result = (TOut)((UntypedEnumerable)object).to_elements();
+            if(typeof(T).is_a(typeof(Enumerable)) && typeof(TOut) == typeof(Elements)) {
+                result = (TOut)((Enumerable)object).to_elements();
                 return true;
             }
             if(assignable_to<TOut>()) {

+ 21 - 20
src/lib/Enumerable.vala

@@ -1,18 +1,7 @@
 
 namespace Invercargill {
 
-    public abstract class UntypedEnumerable : Object {
-        public abstract Type element_type { get; }
-
-        public abstract int count();
-        public abstract TPromotion try_promote_to<TPromotion>() throws PromotionError;
-        public abstract TPromotion promote_to<TPromotion>();
-        public abstract Object[] to_object_array() throws SequenceError;
-        public abstract Elements to_elements();
-
-    }
-
-    public abstract class Enumerable<T> : UntypedEnumerable {
+    public abstract class Enumerable<T> : Object {
 
         public abstract Tracker<T> get_tracker();
 
@@ -65,7 +54,7 @@ namespace Invercargill {
             return array;
         }
 
-        public override int count() {
+        public virtual int count() {
             var count = 0;
             iterate(i => count++);
             return count;
@@ -98,6 +87,10 @@ namespace Invercargill {
             return new FilterQuery<T>(this, (owned)predicate);
         }
 
+        public virtual Enumerable<Tout> try_select<Tout>(owned TryTransformDelegate<T, Tout> transform) {
+            return new TryTransformQuery<T, Tout>(this, (owned)transform);
+        }
+
         public virtual Enumerable<Tout> select<Tout>(owned TransformDelegate<T, Tout> transform) {
             return new TransformQuery<T, Tout>(this, (owned)transform);
         }
@@ -130,6 +123,14 @@ namespace Invercargill {
             return select<Tout>(i => (Tout)i);
         }
 
+        //  public virtual Enumerable<Tout> convert<Tout>() throws ConverterError {
+        //      var converter = get_converter<T, Tout>();
+        //      if(converter == null) {
+        //          throw new ConverterError.CONVERTER_NOT_FOUND(@"No converter registered that can convert from $(typeof(T).name()) to $(typeof(Tout).name())");
+        //      }
+        //      return select(((Converter<T, Tout>)converter).convert);
+        //  }
+
         public virtual Enumerable<Tout> parallel_select<Tout>(owned TransformDelegate<T, Tout> transform, uint workers = 0) {
             var actual_workers = workers;
             if(actual_workers < 1) {
@@ -327,7 +328,7 @@ namespace Invercargill {
             });
         }
 
-        public override Object[] to_object_array() throws SequenceError {
+        public virtual Object[] to_object_array() throws SequenceError {
             if(!typeof(T).is_object()) {
                 throw new SequenceError.INVALID_TYPE("Can only make an object array of an Enumerable<T> where T is derrived from GLib.Object");
             }
@@ -340,11 +341,11 @@ namespace Invercargill {
             return vector;
         }
 
-        public override Type element_type { get {
+        public virtual Type element_type { get {
             return typeof(T);
         }}
 
-        public override TPromotion try_promote_to<TPromotion>() throws PromotionError {
+        public virtual TPromotion try_promote_to<TPromotion>() throws PromotionError {
             var type = typeof(TPromotion);
             if(get_type().is_a(type)) {
                 // Don't promote if we are already target type
@@ -359,8 +360,8 @@ namespace Invercargill {
             if(!type.is_a(typeof(Promotion))) {
                 throw new PromotionError.INVALID_PROMOTION_TYPE(@"Promotion type $(type.name()) does not implement Invercargill.Promotion.");
             }
-            if(!type.is_a(typeof(Enumerable))) {
-                throw new PromotionError.INVALID_PROMOTION_TYPE(@"Promotion type $(type.name()) does not inherit from Invercargill.Enumerable.");
+            if(!type.is_a(typeof(Enumerable)) && typeof(TPromotion) == type) {
+                throw new PromotionError.INVALID_PROMOTION_TYPE(@"Non-resolved promotion type $(type.name()) does not inherit from Invercargill.Enumerable.");
             }
 
             var promotion = Object.new(type);
@@ -371,7 +372,7 @@ namespace Invercargill {
             return ((Promotion)promotion).wrap(this);
         }
 
-        public override TPromotion promote_to<TPromotion>() {
+        public virtual TPromotion promote_to<TPromotion>() {
             try {
                 return try_promote_to<TPromotion>();
             }
@@ -412,7 +413,7 @@ namespace Invercargill {
             return new PositionQuery<T>(this);
         }
 
-        public override Elements to_elements() {
+        public virtual Elements to_elements() {
             var series = new ElementSeries();
             series.add_all(select<Element>(i => new NativeElement<T>(i)));
             return series;

+ 5 - 0
src/lib/Errors.vala

@@ -24,4 +24,9 @@ namespace Invercargill {
     public errordomain ElementError {
         INVALID_CONVERSION,
     }
+
+    //  public errordomain ConverterError {
+    //      CONVERTER_NOT_FOUND,
+    //      INVALID_CONVERSION
+    //  }
 }

+ 0 - 0
src/lib/Associative/KeyValuePair.vala → src/lib/KeyValuePair.vala


+ 0 - 0
src/lib/Associative/PropertyMapper.vala → src/lib/PropertyMapper.vala


+ 29 - 0
src/lib/Queries/TryTransform.vala

@@ -0,0 +1,29 @@
+namespace Invercargill {
+
+    private class TryTransformQuery<Tin, Tout> : BaseQuery<Tin, Tout> {
+        private TryTransformDelegate<Tin, Tout> transform_func;
+
+        public TryTransformQuery(Enumerable<Tin> input, owned TryTransformDelegate<Tin, Tout> func) {
+            this.input = input;
+            transform_func = (owned)func;
+        }
+
+        public override Tracker<Tout> get_tracker() {
+            var tracker = input.get_tracker();
+
+            return new AdvanceTracker<Tout>((out obj) => {
+                while(tracker.has_next()) {
+                    var item = tracker.get_next();
+                    Tout result = null;
+                    if(transform_func(item, ref result)){
+                        obj = result;
+                        return true;
+                    }
+                }
+                obj = null;
+                return false;
+            });
+        }
+    }
+
+}

+ 3 - 2
src/lib/meson.build

@@ -21,6 +21,8 @@ sources += files('Grouping.vala')
 sources += files('Interfaces.vala')
 sources += files('Element.vala')
 sources += files('Converter.vala')
+sources += files('PropertyMapper.vala')
+sources += files('KeyValuePair.vala')
 
 sources += files('Queries/Query.vala')
 sources += files('Queries/Transform.vala')
@@ -32,6 +34,7 @@ sources += files('Queries/Take.vala')
 sources += files('Queries/Parallel.vala')
 sources += files('Queries/Unique.vala')
 sources += files('Queries/Position.vala')
+sources += files('Queries/TryTransform.vala')
 
 sources += files('Concrete/ArrayEnumerable.vala')
 sources += files('Concrete/GeeEnumerable.vala')
@@ -58,12 +61,10 @@ sources += files('Collections/Vector.vala')
 sources += files('Collections/Set.vala')
 
 sources += files('Associative/Associative.vala')
-sources += files('Associative/KeyValuePair.vala')
 sources += files('Associative/Dictionary.vala')
 sources += files('Associative/Index.vala')
 sources += files('Associative/KeyValues.vala')
 sources += files('Associative/Properties.vala')
-sources += files('Associative/PropertyMapper.vala')
 
 invercargill = shared_library('invercargill', sources,
     dependencies: dependencies,

+ 35 - 0
src/tests/Integration/Json.vala

@@ -0,0 +1,35 @@
+using Invercargill;
+using Invercargill.Convert;
+using InvercargillJson;
+using Json;
+
+void json_tests() {
+    Test.add_func("/invercargill/json/strings", () => {
+
+        var json_str = """[
+            "Glengarry",
+            "Gladstone",
+            "Strathern",
+            "Richmond",
+            "Georgetown"
+        ]""";
+
+        try {
+            var suburbs = new JsonElement.from_string(json_str)
+                .assert_as<JsonArray>()
+                .as_strings()
+                .to_array();
+            
+            assert_cmpint(5, CompareOperator.EQ, suburbs.length);
+            assert_cmpstr("Glengarry", CompareOperator.EQ, suburbs[0]);
+            assert_cmpstr("Gladstone", CompareOperator.EQ, suburbs[1]);
+            assert_cmpstr("Strathern", CompareOperator.EQ, suburbs[2]);
+            assert_cmpstr("Richmond", CompareOperator.EQ, suburbs[3]);
+            assert_cmpstr("Georgetown", CompareOperator.EQ, suburbs[4]);
+        }
+        catch(Error e) {
+            assert_no_error(e);
+        }
+    });
+
+}

+ 30 - 27
src/tests/Integration/Promotion.vala

@@ -1,7 +1,6 @@
 using Invercargill;
 using Invercargill.Convert;
 using InvercargillJson;
-using Json;
 
 void promotion_tests() {
 
@@ -26,31 +25,35 @@ void promotion_tests() {
         assert_true(data == new_data);
     });
 
-    Test.add_func("/invercargill/promotions/json", () => {
-
-        var json_object_array = new Json.Node[] {
-            new_string_node("Glengarry"),
-            new_string_node("Gladstone"),
-            new_string_node("Strathern"),
-            new_string_node("Richmond"),
-            new_string_node("Georgetown"),
-        };
-
-        var nodes = ate(json_object_array).promote_to<JsonNodes>();
-        var suburbs = nodes.as_strings().to_array();
-        
-        assert_cmpint(5, CompareOperator.EQ, suburbs.length);
-        assert_cmpstr("Glengarry", CompareOperator.EQ, suburbs[0]);
-        assert_cmpstr("Gladstone", CompareOperator.EQ, suburbs[1]);
-        assert_cmpstr("Strathern", CompareOperator.EQ, suburbs[2]);
-        assert_cmpstr("Richmond", CompareOperator.EQ, suburbs[3]);
-        assert_cmpstr("Georgetown", CompareOperator.EQ, suburbs[4]);
+    Test.add_func("/invercargill/promotion/json", () => {
+
+        var json_str = """[
+            "Glengarry",
+            "Gladstone",
+            "Strathern",
+            "Richmond",
+            "Georgetown"
+        ]""";
+
+        try {
+            var suburbs = new JsonElement.from_string(json_str)
+                .assert_as<JsonArray>()
+                .where(e => e.assert_as<string>().has_prefix("G"))
+                .promote_to<JsonElements>();
+            
+            var names = suburbs
+                .as_strings()
+                .to_array();
+
+            assert_cmpint(3, CompareOperator.EQ, suburbs.count());
+            assert_cmpint(3, CompareOperator.EQ, names.length);
+            assert_cmpstr("Glengarry", CompareOperator.EQ, names[0]);
+            assert_cmpstr("Gladstone", CompareOperator.EQ, names[1]);
+            assert_cmpstr("Georgetown", CompareOperator.EQ, names[2]);
+
+        }
+        catch(Error e) {
+            assert_no_error(e);
+        }
     });
-
-}
-
-private Json.Node new_string_node(string str) {
-    var node = new Json.Node(NodeType.VALUE);
-    node.set_string(str);
-    return node;
 }

+ 43 - 6
src/tests/Integration/PropertyMapper.vala

@@ -21,13 +21,50 @@ void property_mapper_tests() {
 
         var properties = mapper.map_from(original);
 
-        var copy = mapper.materialise(properties);
+        try {
+            var copy = mapper.materialise(properties);
+    
+            assert(properties.where(p => p.value.assignable_to<string>()).no(p => p.value.assert_as<string>() == "Invercargill"));
+            assert(properties.where(p => p.value.assignable_to<string>()).any(p => p.value.assert_as<string>() == "Billy Barrow"));
+            assert_cmpint(original.number, CompareOperator.EQ, copy.number);
+            assert_cmpstr(original.name, CompareOperator.EQ, copy.name);
+            assert_null(copy.not_mapped);
+        }
+        catch(Error e) {
+            assert_no_error(e);
+        }
+
+    });
+
+    Test.add_func("/invercargill/property_mapper/map_from_json", () => {
+
+        var mapper = new PropertyMapperBuilder<MappedClass>()
+            .set_constructor(() => new MappedClass())
+            .map<string>("name", c => c.name, (c, v) => c.name = v)
+            .map<int>("number", c => c.number, (c, v) => c.number = v)
+            .build();
+
+
+        var json_str = """{
+            "number": 25,
+            "name": "Billy Barrow",
+            "not_mapped": "Invercargill"
+        }""";
+
+        try {
+            var properties = new JsonElement.from_string(json_str)
+                .assert_as<JsonObject>();
+    
+            var copy = mapper.materialise(properties);
+            assert(properties.where(p => p.value.assignable_to<string>()).any(p => p.value.assert_as<string>() == "Billy Barrow"));
+            assert_cmpint(25, CompareOperator.EQ, copy.number);
+            assert_cmpstr("Billy Barrow", CompareOperator.EQ, copy.name);
+            assert_null(copy.not_mapped);
+        }
+        catch(Error e) {
+            assert_no_error(e);
+        }
 
-        assert(properties.where(p => p.value.assignable_to<string>()).no(p => p.value.as<string>() == "Invercargill"));
-        assert(properties.where(p => p.value.assignable_to<string>()).any(p => p.value.as<string>() == "Billy Barrow"));
-        assert_cmpint(original.number, CompareOperator.EQ, copy.number);
-        assert_cmpstr(original.name, CompareOperator.EQ, copy.name);
-        assert_null(copy.not_mapped);
 
     });
 

+ 1 - 0
src/tests/TestRunner.vala

@@ -19,6 +19,7 @@ public static int main(string[] args) {
     numbers_test();
     dictionary_tests();
     property_mapper_tests();
+    json_tests();
     
     Test.run();
     

+ 1 - 0
src/tests/meson.build

@@ -18,6 +18,7 @@ sources += files('Integration/Promotion.vala')
 sources += files('Integration/Numbers.vala')
 sources += files('Integration/Dictionary.vala')
 sources += files('Integration/PropertyMapper.vala')
+sources += files('Integration/Json.vala')
 
 sources += files('Speed/SpeedTest.vala')
 sources += files('Speed/Series.vala')