X-Git-Url: http://nitlanguage.org diff --git a/lib/json/serialization.nit b/lib/json/serialization.nit index f7c161e..3707b19 100644 --- a/lib/json/serialization.nit +++ b/lib/json/serialization.nit @@ -55,11 +55,42 @@ # assert alice.to_plain_json == """ # {"name": "Alice", "year_of_birth": 1978, "next_of_kin": {"name": "Bob", "year_of_birth": 1986, "next_of_kin": null}}""" # ~~~ +# +# ## JSON to Nit objects +# +# The `JsonDeserializer` support reading JSON code with minimal meta-data +# to easily create Nit object from client-side code or configuration files. +# Each JSON object must define the `__class` attribute with the corresponding +# Nit class and the expected attributes with its name in Nit followed by its value. +# +# ### Usage Example +# +# ~~~nitish +# import json::serialization +# +# class MeetupConfig +# serialize +# +# var description: String +# var max_participants: nullable Int +# var answers: Array[FlatString] +# end +# +# var json_code = """ +# {"__class": "MeetupConfig", "description": "My Awesome Meetup", "max_participants": null, "answers": ["Pepperoni", "Chicken"]}""" +# var deserializer = new JsonDeserializer(json_code) +# +# var meet = deserializer.deserialize +# assert meet isa MeetupConfig +# assert meet.description == "My Awesome Meetup" +# assert meet.max_participants == null +# assert meet.answers == ["Pepperoni", "Chicken"] +# ~~~ module serialization import ::serialization::caching private import ::serialization::engine_tools -private import static +import static # Serializer of Nit objects to Json string. class JsonSerializer @@ -256,12 +287,17 @@ class JsonDeserializer end end - if not object.keys.has("__class") then + var class_name = object.get_or_null("__class") + if class_name == null then + # Fallback to custom heuristic + class_name = class_name_heuristic(object) + end + + if class_name == null then errors.add new Error("Serialization Error: JSON object declaration does not declare a `__class`.") return object end - var class_name = object["__class"] if not class_name isa String then errors.add new Error("Serialization Error: JSON object declaration declares a non-string `__class`.") return object @@ -301,10 +337,56 @@ class JsonDeserializer return object end + # Simple JSON array without serialization metadata if object isa Array[nullable Object] then - # special case, isa Array[nullable Serializable] - var array = new Array[nullable Serializable] - for e in object do array.add e.as(nullable Serializable) + var array = new Array[nullable Object] + var types = new HashSet[String] + var has_nullable = false + for e in object do + var res = convert_object(e) + array.add res + + if res != null then + types.add res.class_name + else has_nullable = true + end + + if types.length == 1 then + var array_type = types.first + + var typed_array + if array_type == "FlatString" then + if has_nullable then + typed_array = new Array[nullable FlatString] + else typed_array = new Array[FlatString] + else if array_type == "Int" then + if has_nullable then + typed_array = new Array[nullable Int] + else typed_array = new Array[Int] + else if array_type == "Float" then + if has_nullable then + typed_array = new Array[nullable Float] + else typed_array = new Array[Float] + else + # TODO support all array types when we separate the constructor + # `from_deserializer` from the filling of the items. + + if not has_nullable then + typed_array = new Array[Object] + else + # Unsupported array type, return as `Array[nullable Object]` + return array + end + end + + assert typed_array isa Array[nullable Object] + + # Copy item to the new array + for e in array do typed_array.add e + return typed_array + end + + # Uninferable type, return as `Array[nullable Object]` return array end @@ -316,6 +398,54 @@ class JsonDeserializer errors.clear return convert_object(root) end + + # User customizable heuristic to get the name of the Nit class to deserialize `json_object` + # + # This method is called only when deserializing an object without the metadata `__class`. + # Return the class name as a `String` when it can be inferred. + # Return `null` when the class name cannot be found. + # + # If a valid class name is returned, `json_object` will then be deserialized normally. + # So it must contain the attributes of the corresponding class, as usual. + # + # ~~~nitish + # class MyData + # serialize + # + # var data: String + # end + # + # class MyError + # serialize + # + # var error: String + # end + # + # class MyJsonDeserializer + # super JsonDeserializer + # + # redef fun class_name_heuristic(json_object) + # do + # if json_object.keys.has("error") then return "MyError" + # if json_object.keys.has("data") then return "MyData" + # return null + # end + # end + # + # var json = """{"data": "some other data"}""" + # var deserializer = new MyJsonDeserializer(json) + # var deserialized = deserializer.deserialize + # assert deserialized isa MyData + # + # json = """{"error": "some error message"}""" + # deserializer = new MyJsonDeserializer(json) + # deserialized = deserializer.deserialize + # assert deserialized isa MyError + # ~~~ + protected fun class_name_heuristic(json_object: JsonObject): nullable String + do + return null + end end redef class Serializable @@ -411,9 +541,7 @@ redef class SimpleCollection[E] v.stream.write id.to_s v.stream.write """, "__class": """" v.stream.write class_name - v.stream.write """", "__length": """ - v.stream.write length.to_s - v.stream.write """, "__items": """ + v.stream.write """", "__items": """ end serialize_to_pure_json v @@ -430,28 +558,15 @@ redef class SimpleCollection[E] v.notify_of_creation self init - var length = v.deserialize_attribute("__length").as(Int) var arr = v.path.last["__items"].as(SequenceRead[nullable Object]) - for i in length.times do - var obj = v.convert_object(arr[i]) + for o in arr do + var obj = v.convert_object(o) self.add obj end end end end -redef class Array[E] - redef fun serialize_to_json(v) - do - if v.plain_json or class_name == "Array[nullable Serializable]" then - # Using class_name to get the exact type, - # we do not want Array[Int] or anything else here. - - serialize_to_pure_json v - else super - end -end - redef class Map[K, V] redef fun serialize_to_json(v) do