Skip to content

Commit

Permalink
Merge: lib/json: unify the writing API using serialization only
Browse files Browse the repository at this point in the history
Some context first, before this PR, there were 2 API to write JSON:
* The module `static` provided `to_json` and `to_pretty_json` which supported writing only basic Nit types associated with JSON types and objects with a manually implemented `to_json`.
* The module `serialization` provides `serialize_to_json` which supports all `Serializable` types, a superset of the JSON types, and where standard Nit objects are serialized to JSON objects by generated code.

The advantages of the `serialization` API are:
* Objects written to JSON with some metadata (mainly the name of the Nit type) can be deserialized back to Nit objects.
* The boilerplate code to write the attributes is generated, usually a single `is serialize` after the module declarations does the trick. (`core_serialize_to`, the equivalent of `to_json`, doesn't have to be implemented in each classes, the generated version is usually enough.)
* Implementing `core_serialize_to` (or leaving it to the generated code) instead of `to_json` makes the object compatible with both the JSON and binary serialization services.
* In general, the `serialization` API allows to easily write Nit objects to JSON and build JSON objects using `Map` instances:
~~~
import json::serialization

class Person
    serialize

    var name: String
    var year_of_birth: Int
end

var bob = new Person("Bob", 1986)
assert bob.serialize_to_json(pretty=true, plain=true) == """
{
    "name": "Bob",
    "year_of_birth": 1986
}"""

var charlie = new Map[String, nullable Serializable]
charlie["name"] = "Charlie"
charlie["year_of_birth"] = 1968
assert charlie.serialize_to_json(pretty=true, plain=true) == """
{
    "name": "Charlie",
    "year_of_birth": 1968
}"""
~~~

So this PR drops the `static` writing API, and replaces its services (`to_json` and `to_pretty_json`) by aliases in `serialization`. Some subclasses have been updated to implement the recursive `serialize_to` instead of the `to_json`, the changes are minimal to avoid breaking any software, but they are not optimal. The result is a single JSON writing API!

The generated JSON should not have changed much (maybe a bit less or more white spaces), except for some error messages which were translated to invalid JSON and are now serialized to a valid JSON object.

I expect some tests to fail as this is a big change, and clients of the `json::static` writing services (@Morriar) should probably double check my changes to their code.

In the future, I'm thinking of dropping the name `serialize_to_json` and `to_pretty_json` and move the optionnal parameters to `to_json`. The JSON module also really needs refactoring at this point.

Pull-Request: #2312
Reviewed-by: Alexandre Terrasa <[email protected]>
Reviewed-by: Romain Chanoir <[email protected]>
Reviewed-by: Jean Privat <[email protected]>
Reviewed-by: Lucas Bajolet <[email protected]>
  • Loading branch information
privat committed Sep 23, 2016
2 parents b98dde1 + 88b5b3f commit bcd8759
Show file tree
Hide file tree
Showing 99 changed files with 1,343 additions and 1,696 deletions.
2 changes: 1 addition & 1 deletion benchmarks/json/scripts/nitcc_parser.nit
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import json::static

var text = args.first.to_path.read_all
var json = text.parse_json
2 changes: 1 addition & 1 deletion contrib/benitlux/src/client/base.nit
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import app::ui
import app::data_store
import app::http_request
import android::aware
import json::serialization
import json

import benitlux_model
import translations
Expand Down
2 changes: 1 addition & 1 deletion contrib/benitlux/src/server/benitlux_controller.nit
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module benitlux_controller

import nitcorn
import nitcorn::restful
private import json::serialization
private import json

import benitlux_model
import benitlux_db
Expand Down
5 changes: 3 additions & 2 deletions contrib/neo_doxygen/src/model/descriptions.nit
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
module model::descriptions

import json::static
import json

# Documentation associated to an entity.
#
Expand Down Expand Up @@ -106,8 +107,8 @@ class Documentation
# Is the documentation empty?
fun is_empty: Bool do return content.is_empty

redef fun to_json do return content.to_json
redef fun append_json(b) do content.append_json(b)
redef fun serialize_to(v) do content.serialize_to v
redef fun accept_json_serializer(v) do content.serialize_to v
end

# A `Jsonable` array of strings.
Expand Down
8 changes: 4 additions & 4 deletions contrib/neo_doxygen/src/model/graph.nit
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ end
abstract class Compound
super Entity

# Set the declared visibility (the proctection) of the compound.
# Set the declared visibility (the protection) of the compound.
fun visibility=(visibility: String) do
self["visibility"] = visibility
end
Expand All @@ -233,7 +233,7 @@ abstract class Compound

# Declare an inner namespace.
#
# Note: Althought Doxygen indicates that the name is optional,
# Note: Although Doxygen indicates that the name is optional,
# declarations with an empty name are not supported yet, except for the root
# namespace. For the root namespace, both arguments are empty.
#
Expand All @@ -246,7 +246,7 @@ abstract class Compound

# Declare an inner class.
#
# Note: Althought Doxygen indicates that both arguments are optional,
# Note: Although Doxygen indicates that both arguments are optional,
# declarations with an empty ID are not supported yet.
#
# Parameters:
Expand Down Expand Up @@ -292,7 +292,7 @@ class Namespace
self.labels.add("MGroup")
end

redef fun declare_namespace(id: String, full_name: String) do
redef fun declare_namespace(id, full_name) do
inner_namespaces.add new NamespaceRef(id, full_name)
end

Expand Down
9 changes: 6 additions & 3 deletions contrib/neo_doxygen/src/model/location.nit
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
module location

import json::static
import json

# A location inside a source file.
class Location
Expand All @@ -36,11 +37,13 @@ class Location
# The one-based column index of the last character.
var column_end: Int = 1 is writable

redef fun to_s: String do
redef fun to_s do
var path = path
var file_part = "/dev/null:"
if path != null and path.length > 0 then file_part = "{path.as(not null)}:"
if path != null and path.length > 0 then file_part = "{path}:"
return "{file_part}{line_start},{column_start}--{line_end},{column_end}"
end

redef fun to_json do return to_s.to_json
redef fun serialize_to(v) do to_s.serialize_to v
redef fun accept_json_serializer(v) do to_s.serialize_to v
end
44 changes: 0 additions & 44 deletions contrib/neo_doxygen/src/tests/neo_doxygen_descriptions.nit

This file was deleted.

This file was deleted.

2 changes: 1 addition & 1 deletion contrib/nitrpg/src/achievements.nit
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class Achievement
json["reward"].as(Int))
end

redef fun to_json do
redef fun to_json_object do
var json = super
json["id"] = id
json["name"] = name
Expand Down
2 changes: 1 addition & 1 deletion contrib/nitrpg/src/events.nit
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class GameEvent
time = new ISODate.from_string(json["time"].as(String))
end

redef fun to_json do
redef fun to_json_object do
var json = new JsonObject
json["internal_id"] = internal_id.to_s
json["kind"] = kind
Expand Down
10 changes: 5 additions & 5 deletions contrib/nitrpg/src/game.nit
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ interface GameEntity
fun key: String is abstract

# Saves `self` in db.
fun save do game.db.collection(collection_name).save(to_json)
fun save do game.db.collection(collection_name).save(to_json_object)

# Json representation of `self`.
fun to_json: JsonObject do
fun to_json_object: JsonObject do
var json = new JsonObject
json["_id"] = key
return json
Expand Down Expand Up @@ -102,7 +102,7 @@ class Game
# Used to load entities from saved data.
fun from_json(json: JsonObject) do end

redef fun to_json do
redef fun to_json_object do
var json = super
json["name"] = name
return json
Expand Down Expand Up @@ -166,7 +166,7 @@ class Game
end

# Erase all saved data for this game.
fun clear do db.collection(collection_name).remove(to_json)
fun clear do db.collection(collection_name).remove(to_json_object)

# Verbosity level used fo stdout.
#
Expand Down Expand Up @@ -236,7 +236,7 @@ class Player
nitcoins = json["nitcoins"].as(Int)
end

redef fun to_json do
redef fun to_json_object do
var json = super
json["game"] = game.key
json["name"] = name
Expand Down
2 changes: 1 addition & 1 deletion contrib/nitrpg/src/statistics.nit
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ class GameStats
for k, v in values do self[k] = v.as(Int)
end

redef fun to_json do
redef fun to_json_object do
var obj = super
obj["period"] = period
obj["owner"] = owner.key
Expand Down
2 changes: 1 addition & 1 deletion contrib/nitrpg/src/test_achievements.nit
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class TestGame
var a3 = new Achievement(game, "test_id3", "test_name", "test_desc", 15)
game.add_achievement(a1)
game.add_achievement(a2)
game.db.collection("achievements").insert(a3.to_json)
game.db.collection("achievements").insert(a3.to_json_object)
var ok = [a1.id, a2.id]
var res = game.load_achievements
assert res.length == 2
Expand Down
4 changes: 2 additions & 2 deletions contrib/nitrpg/src/test_events.nit
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class TestGame
var event3 = new GameEvent(game, "test_kind", new JsonObject)
game.add_event(event1)
game.add_event(event2)
game.db.collection("events").insert(event3.to_json)
game.db.collection("events").insert(event3.to_json_object)
var ok = [event1.internal_id, event2.internal_id]
var res = game.load_events
assert res.length == 2
Expand Down Expand Up @@ -115,7 +115,7 @@ class TestGameEvent
var db = gen_test_db
var game = load_game("Morriar/nit", db)
var event = new GameEvent(game, "test_kind", new JsonObject)
assert event.to_json["kind"] == "test_kind"
assert event.to_json_object["kind"] == "test_kind"
end

fun test_init_from_json do
Expand Down
10 changes: 5 additions & 5 deletions contrib/nitrpg/src/test_game.nit
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ class TestGame

var player1 = new Player(game, "Morriar")
var player2 = new Player(ogame, "privat")
game.db.collection("players").insert(player1.to_json)
ogame.db.collection("players").insert(player2.to_json)
game.db.collection("players").insert(player1.to_json_object)
ogame.db.collection("players").insert(player2.to_json_object)

assert game.load_player("privat") == null
assert game.load_player("Morriar").name == "Morriar"
Expand All @@ -60,9 +60,9 @@ class TestGame
var player1 = new Player(game, "Morriar")
var player2 = new Player(ogame, "privat")
var player3 = new Player(game, "xymus")
game.db.collection("players").insert(player1.to_json)
ogame.db.collection("players").insert(player2.to_json)
game.db.collection("players").insert(player3.to_json)
game.db.collection("players").insert(player1.to_json_object)
ogame.db.collection("players").insert(player2.to_json_object)
game.db.collection("players").insert(player3.to_json_object)

var players = game.load_players
var ok = ["Morriar", "xymus"]
Expand Down
8 changes: 5 additions & 3 deletions contrib/refund/src/refund_json.nit
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ module refund_json

import refund_base
import json::static
import json

redef class RefundProcessor

Expand Down Expand Up @@ -89,6 +90,7 @@ redef class RefundProcessor
fun write_output(str: String, file: String) do
var ofs = new FileWriter.open(file)
ofs.write(str)
ofs.write("\n")
ofs.close
end

Expand Down Expand Up @@ -118,7 +120,7 @@ redef class RefundProcessor
exit 1
end

redef fun show_stats do print load_stats.to_json.to_pretty_json
redef fun show_stats do print load_stats.to_json_object.to_pretty_json

redef fun load_stats do
# If no stats found, return a new object
Expand All @@ -134,7 +136,7 @@ redef class RefundProcessor
end

redef fun save_stats(stats) do
write_output(stats.to_json.to_pretty_json, stats_file)
write_output(stats.to_json_object.to_pretty_json, stats_file)
end
end

Expand All @@ -146,7 +148,7 @@ redef class RefundStats
end

# Outputs `self` as a JSON string.
fun to_json: JsonObject do
fun to_json_object: JsonObject do
var obj = new JsonObject
for k, v in self do obj[k] = v
return obj
Expand Down
2 changes: 1 addition & 1 deletion contrib/refund/tests/res/json_error1.res
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"message": "Wrong input file (Unexpected Eof; is acceptable instead: value)"
"message": "Wrong input file (Empty JSON)"
}
2 changes: 1 addition & 1 deletion contrib/refund/tests/res/json_error3.res
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"message": "Wrong input file (Unexpected Eof; is acceptable instead: members, pair)"
"message": "Wrong input file (Malformed JSON object)"
}
3 changes: 1 addition & 2 deletions contrib/shibuqam/examples/shibuqamoauth.nit
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ module shibuqamoauth

import popcorn
import shibuqam
import json::serialization
import json

redef class HttpRequest
# percent decoded get or post parameter.
Expand Down Expand Up @@ -304,7 +304,6 @@ end

redef class User
super Jsonable
redef fun to_json do return serialize_to_json(plain=true)
end

# Information about an authenticated used stored on the server to be given to the client.
Expand Down
2 changes: 1 addition & 1 deletion contrib/tinks/src/client/linux_client.nit
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module linux_client

import mnit::linux
import linux::audio
import json::serialization
import json

import client

Expand Down
2 changes: 1 addition & 1 deletion contrib/tnitter/src/action.nit
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
module action

import nitcorn
import json::serialization
import json

import model
import database
Expand Down
Loading

0 comments on commit bcd8759

Please sign in to comment.