lib: intro of json serialization module
authorAlexis Laferrière <alexis.laf@xymus.net>
Thu, 4 Aug 2011 18:33:21 +0000 (14:33 -0400)
committerAlexis Laferrière <alexis.laf@xymus.net>
Thu, 4 Apr 2013 16:13:10 +0000 (12:13 -0400)
Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

lib/json/json.nit [new file with mode: 0644]
lib/json/json_reader.nit [new file with mode: 0644]
lib/json/json_writer.nit [new file with mode: 0644]
lib/json/jsonable.nit [new file with mode: 0644]
lib/json/jsonable.nit.args [new file with mode: 0644]
tests/nitg-e.skip
tests/nitg-s.skip
tests/nitg.skip
tests/niti.skip
tests/sav/test_json.res [new file with mode: 0644]
tests/test_json.nit [new file with mode: 0644]

diff --git a/lib/json/json.nit b/lib/json/json.nit
new file mode 100644 (file)
index 0000000..1e9522f
--- /dev/null
@@ -0,0 +1,34 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2012-2013 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Writing to and reading from the Json format.
+# Based on the json0 C library
+module json
+
+import jsonable
+import json_reader
+import json_writer
+
+redef class String
+       fun json_load_from_file : nullable Map[ String, nullable Jsonable ]
+       do
+               var f = new IFStream.open( self )
+               var data = f.read_all.json_to_object
+               f.close
+
+               return data.as(not null) # ( Map[ String, nullable Jsonable ] )
+       end
+end
diff --git a/lib/json/json_reader.nit b/lib/json/json_reader.nit
new file mode 100644 (file)
index 0000000..3995f13
--- /dev/null
@@ -0,0 +1,122 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2012-2013 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# This file is free software, which comes along with NIT.  This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without  even  the implied warranty of  MERCHANTABILITY or  FITNESS FOR A 
+# PARTICULAR PURPOSE.  You can modify it is you want,  provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You  are  allowed  to  redistribute it and sell it, alone or is a part of
+# another product.
+
+# Deserialisation from the Json format to Nit objects
+module json_reader
+
+intrude import jsonable
+
+redef class String
+       # Deserializes this String and return its value as a Map[String, nullable Jsonable]
+       # On error, null is returned.
+       fun json_to_object : nullable Map[String, nullable Jsonable] import String::from_cstring, JsonObject::json_to_map `{
+               char *native_recv;
+               json_object *jobj;
+               nullable_Map map;
+
+               native_recv = String_to_cstring( recv );
+               jobj = json_tokener_parse( native_recv );
+               map = JsonObject_json_to_map( jobj );
+
+               /*json_object_put( jobj );*/
+               return map;
+       `}
+end
+
+redef extern JsonObject
+       # Get this json object as a Map
+       private fun json_to_map : nullable Map[String, nullable Jsonable] import String::from_cstring, String::to_cstring, HashMap, HashMap::[]=, json_cross, HashMap[String,nullable Jsonable] as( nullable Map[String,nullable Jsonable] ), String as ( Object ), nullable Jsonable as (nullable Object) `{
+               HashMap map;
+               String nit_key;
+               nullable_Jsonable nit_val;
+
+               map = new_HashMap();
+
+               { /* prevents "mixed declaration and code" warning for C90 */
+               json_object_object_foreach( recv, key, val ) {
+                       nit_key = new_String_from_cstring( key );
+                       nit_val = JsonObject_json_cross( val , json_object_get_type( val ) );
+
+                       HashMap__index_assign( map, String_as_Object( nit_key ), nullable_Jsonable_as_nullable_Object( nit_val ) );
+               }
+               }
+
+               return HashMap_as_nullable_Map( map );
+       `}
+
+       # Get this json object as a Bool
+       private fun json_to_bool : Bool `{
+               return json_object_get_boolean( recv );
+       `}
+
+       # Get this json object as a Float
+       private fun json_to_float : Float `{
+               return json_object_get_double( recv );
+       `}
+
+       # Get this json object as an Int
+       private fun json_to_int : Int `{
+               return json_object_get_int( recv );
+       `}
+
+       # Get this json object as a Sequence
+       private fun json_to_sequence : Sequence[Jsonable] import Array, Array::push, Array[nullable Jsonable] as ( Sequence[nullable Jsonable] ), json_cross `{
+               array_list* jlist;
+               json_object* jobj;
+               nullable_Jsonable obj;
+               Array dest;
+               int i;
+               int len;
+
+               jlist = json_object_get_array( recv );
+               len = json_object_array_length( recv );
+               dest = new_Array();
+               for ( i = 0; i < len; i ++ ) {
+                       jobj = json_object_array_get_idx( recv, i );
+                       obj = JsonObject_json_cross( jobj, json_object_get_type( jobj ) );
+                       Array_push( dest, nullable_Jsonable_as_nullable_Object( obj ) );
+               }
+
+               return Array_as_Sequence( dest );
+       `}
+
+       # Get this json object as a String
+       private fun json_to_string : String import String::from_cstring `{
+               char *cstring;
+               cstring = json_object_get_string( recv );
+               return new_String_from_cstring( cstring );
+       `}
+
+       # Intermediate function to convert to gt this Json object as a given type.
+       # Imlemented in Nit because Nit should manage all possible typing-related work.
+       private fun json_cross( json_type : Int ) : nullable Jsonable
+       do
+               if json_type == 0 then # null
+                       return null
+               else if json_type == 1 then # Bool
+                       return json_to_bool
+               else if json_type == 2 then # Float
+                       return json_to_float
+               else if json_type == 3 then # Int
+                       return json_to_int
+               else if json_type == 4 then # Map
+                       return json_to_map
+               else if json_type == 5 then # Sequence
+                       return json_to_sequence
+               else if json_type == 6 then # String
+                       return json_to_string
+               else
+                       print "WARNING: Unrecongnized json object type id: {json_type}"
+                       return null
+               end
+       end
+end
diff --git a/lib/json/json_writer.nit b/lib/json/json_writer.nit
new file mode 100644 (file)
index 0000000..81946f3
--- /dev/null
@@ -0,0 +1,143 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2012-2013 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# This file is free software, which comes along with NIT.  This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without  even  the implied warranty of  MERCHANTABILITY or  FITNESS FOR A 
+# PARTICULAR PURPOSE.  You can modify it is you want,  provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You  are  allowed  to  redistribute it and sell it, alone or is a part of
+# another product.
+
+# Serialisation from Nit objects to the Json format
+module json_writer
+
+intrude import jsonable
+
+redef interface Jsonable
+       # Get a JsonObject representing this instance, specific to the C library
+       private fun to_json_object : JsonObject is abstract
+end
+
+# Will ignore non-jsonable
+redef class Map[ K, V ]
+       # Get a json-formatted string of this map
+       fun to_json( pretty: Bool ) : String import to_json_object `{
+               json_object *jobj;
+               char *json_native_string;
+               String json_string;
+
+               jobj = Map_to_json_object( recv );
+               if ( pretty )
+                       json_native_string = json_object_to_json_string_ext( jobj, JSON_C_TO_STRING_PRETTY );
+               else
+                       json_native_string = json_object_to_json_string_ext( jobj, JSON_C_TO_STRING_PLAIN );
+               json_string = new_String_from_cstring( json_native_string );
+               return json_string;
+       `}
+
+       redef fun to_json_object
+       do
+               var jobj = new JsonObject
+
+               var iter = iterator
+               while iter.is_ok do
+                       var key = iter.key
+                       if key isa String then
+                               var val = iter.item
+                               if val isa Jsonable then
+                                       var jsubobj = val.to_json_object
+                                       jobj.add( key, jsubobj )
+                               else if val == null then
+                                       jobj.add( key, null )
+                               else
+                                       print "WARNING: value \"{val}\" not jsonable, cannot be converted to json."
+                               end
+                       else
+                               print "WARNING: key \"{key}\" not a string, cannot be converted to json."
+                       end
+
+                       iter.next
+               end
+               return jobj
+       end
+end
+
+redef class Sequence[ E ]
+       redef fun to_json_object
+       do
+               var jarray = new JsonArray
+               for e in self do
+                       if e isa nullable Jsonable then
+                               if e == null then
+                                       jarray.push( null )
+                               else
+                                       var obj = e.to_json_object
+                                       jarray.push( obj )
+                               end
+                       else
+                               print "WARNING: element \"{e}\" not a Jsonable, cannot be converted to json."
+                       end
+               end
+
+               return jarray
+       end
+end
+
+redef class String
+       redef fun to_json_object import String::from_cstring `{
+               char *native_recv = String_to_cstring( recv );
+               return json_object_new_string( native_recv );
+       `}
+end
+
+redef class Int
+       redef fun to_json_object `{
+               return json_object_new_int( recv );
+       `}
+end
+
+redef class Bool
+       redef fun to_json_object `{
+               return json_object_new_boolean( recv );
+       `}
+end
+
+redef class Float
+       redef fun to_json_object `{
+               return json_object_new_double( recv );
+       `}
+end
+
+redef class JsonObject
+       new `{ return json_object_new_object(); `}
+
+       # Add a key and value to the object
+       fun add( key : String, val : nullable JsonObject ) import String::to_cstring, nullable JsonObject as not nullable `{
+               char* native_key;
+
+               native_key = String_to_cstring( key );
+
+               if ( JsonObject_is_null(val) ) {
+                       json_object_object_add( recv, native_key, NULL );
+               } else {
+                       json_object *jobj;
+                       jobj = JsonObject_as_not_null( val );
+                       json_object_object_add( recv, native_key, jobj );
+               }
+       `}
+end
+
+private extern JsonArray
+       super JsonObject
+
+       new `{ return json_object_new_array(); `}
+
+       fun push( val : nullable JsonObject ) `{
+               if ( JsonObject_is_null(val) )
+                       json_object_array_add( recv, NULL );
+               else
+                       json_object_array_add( recv, JsonObject_as_not_null(val) );
+       `}
+end
diff --git a/lib/json/jsonable.nit b/lib/json/jsonable.nit
new file mode 100644 (file)
index 0000000..2a87ed9
--- /dev/null
@@ -0,0 +1,61 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2012-2013 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Basic json related functionalities
+module jsonable
+
+in "C Header" `{
+       #define __STRICT_ANSI__
+       #include <json/json.h>
+`}
+
+# Type supported by the Json format
+interface Jsonable
+end
+
+# Main object type used by C library
+private extern JsonObject `{ json_object* `}
+       # Give up ownership of this object and decrease the reference count.
+       fun put `{ json_object_put( recv ); `}
+
+       # Aquire ownership of this object and increase the reference count.
+       fun get `{ json_object_get( recv ); `}
+end
+
+redef class Sequence[ V ]
+       super Jsonable
+end
+
+# Can b converted to a Json object
+redef class Map[ K, V ]
+       super Jsonable
+end
+
+redef class String
+       super Jsonable
+end
+
+redef class Int
+       super Jsonable
+end
+
+redef class Bool
+       super Jsonable
+end
+
+redef class Float
+       super Jsonable
+end
diff --git a/lib/json/jsonable.nit.args b/lib/json/jsonable.nit.args
new file mode 100644 (file)
index 0000000..a21170f
--- /dev/null
@@ -0,0 +1 @@
+--cc-lib-name json
index 649d79e..8f01cfc 100644 (file)
@@ -6,6 +6,7 @@ test_extern
 test_ni_
 test_ffi_
 test_gtk
+test_json
 calculator
 nitc
 nitdoc
index 649d79e..8f01cfc 100644 (file)
@@ -6,6 +6,7 @@ test_extern
 test_ni_
 test_ffi_
 test_gtk
+test_json
 calculator
 nitc
 nitdoc
index 649d79e..8f01cfc 100644 (file)
@@ -6,6 +6,7 @@ test_extern
 test_ni_
 test_ffi_
 test_gtk
+test_json
 calculator
 nitc
 nitdoc
index cdc3c9a..1aae86b 100644 (file)
@@ -7,6 +7,7 @@ test_ffi_
 test_math
 test_mem
 test_gtk
+test_json
 calculator
 shoot_logic
 bench_
diff --git a/tests/sav/test_json.res b/tests/sav/test_json.res
new file mode 100644 (file)
index 0000000..a20ecbf
--- /dev/null
@@ -0,0 +1,40 @@
+../lib/json/json_reader.nit: In function 'JsonObject_json_to_string___impl':
+../lib/json/json_reader.nit:96:11: warning: assignment discards 'const' qualifier from pointer target type [enabled by default]
+../lib/json/json_writer.nit: In function 'Map_to_json___impl':
+../lib/json/json_writer.nit:34:23: warning: assignment discards 'const' qualifier from pointer target type [enabled by default]
+../lib/json/json_writer.nit:36:23: warning: assignment discards 'const' qualifier from pointer target type [enabled by default]
+{"int":"1234","float":"0.1234","str":"str","null": null}
+{"int":1234,"float":0.123400,"str":"str","null":null}
+{
+  "int":1234,
+  "float":0.123400,
+  "str":"str",
+  "null":null
+}
+{"arr":"123","obj":"{"int":"123","float":"-234.449997"}"}
+{"arr":[1,2,3],"obj":{"int":123,"float":-234.449997}}
+{
+  "arr":[
+    1,
+    2,
+    3
+  ],
+  "obj":{
+    "int":123,
+    "float":-234.449997
+  }
+}
+{"arr":"12.3str","obj":"{"int":"123","float":"-234.449997"}"}
+{"arr":[1,2.300000,null,"str"],"obj":{"int":123,"float":-234.449997}}
+{
+  "arr":[
+    1,
+    2.300000,
+    null,
+    "str"
+  ],
+  "obj":{
+    "int":123,
+    "float":-234.449997
+  }
+}
diff --git a/tests/test_json.nit b/tests/test_json.nit
new file mode 100644 (file)
index 0000000..1a06290
--- /dev/null
@@ -0,0 +1,68 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2012-2013 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module test_json
+
+import json
+
+redef class HashMap[K,V]
+       redef fun to_s
+       do
+               var es = new Array[String]
+               for k, v in self do
+                       if v != null then
+                               es.add( "\"{k}\":\"{v}\"" )
+                       else
+                               es.add( "\"{k}\": null" )
+                       end
+               end
+               return "\{{es.join(",")}\}"
+       end
+end
+
+redef class String
+       fun parse_and_display
+       do
+               var json_map = json_to_object
+               if json_map != null then
+                       print json_map
+                       print json_map.to_json(false)
+                       print json_map.to_json(true)
+               else
+                       print "Conversion to json failed."
+               end
+       end
+end
+
+fun print_usage do print "Usage: json input.json"
+
+if args.length == 1 then
+       var input_path = args.first
+       var input_file = new IFStream.open( input_path )
+       var input_text = input_file.read_all
+       input_file.close
+
+       input_text.parse_and_display
+else
+       var s = "\{\"int\":1234, \"float\":0.1234, \"str\":\"str\", \"null\":null\}"
+       s.parse_and_display
+
+       s = "\{\"arr\":[1,2,3], \"obj\":\{\"int\":123, \"float\":-234.45\}\}"
+       s.parse_and_display
+
+       s = "\{\"arr\":[1,2.3,null,\"str\"], \"obj\":\{\"int\":123, \"float\":-234.45\}\}"
+       s.parse_and_display
+end