Changes the following warning to an advice and raise it at each use of the annotations (instead of only once).
~~~
/nit/lib/gamnit/flat.nit:355,9--26: Warning: program `glslangValidator` not in PATH, cannot validate this shader. (glslvalidator)
""" @ glsl_vertex_shader
~~~
Pull-Request: #2299
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
redef class App
# Sound
- var soundsp: Sound
+ var soundsp: Sound is noinit
# Music
- var soundmp: Music
+ var soundmp: Music is noinit
# Sound
var easy_soundsp = new Sound("testsound")
super
default_mediaplayer.looping = true
if test_assets then
- soundsp = load_sound("testsound.og")
- soundmp = load_music("xylofon.og")
+ soundsp = new Sound("testsound.og")
+ soundmp = new Music("xylofon.og")
soundmp.play
end
if test_ressources then
var resval = nsoundpool.load_path(path.to_java_string, priority)
sys.jni_env.pop_local_frame
if resval == -1 then
- self.error = new Error("Unable to load sound from path : " + path)
+ self.error = new Error("Unable to load sound from path: " + path)
return new Sound.priv_init(null, -1, self, self.error)
else
return new Sound.priv_init(null, resval, self, null)
return nsoundpool.play(id, left_volume, right_volume, priority, looping, rate)
end
- # Load a sound by its name in the resources, the sound must be in the `res/raw` folder
- fun load_name(resource_manager: ResourcesManager, context: NativeActivity, sound: String): Sound do
- var id = resource_manager.raw_id(sound)
+ # Load a sound by its `name` in the resources, the sound must be in the `res/raw` folder
+ fun load_name(resource_manager: ResourcesManager, context: NativeActivity, name: String): Sound do
+ var id = resource_manager.raw_id(name)
var resval = nsoundpool.load_id(context, id, priority)
if resval == -1 then
- self.error = new Error("Unable to load sound from resources : " + sound)
+ self.error = new Error("Unable to load sound from resources: " + name)
return new Sound.priv_init(null, -1, self, self.error)
else
return new Sound.priv_init(null, resval, self, null)
# Used when the app pause all sounds or resume all sounds
var paused: Bool = false
- redef init do add_to_sounds(self)
+ redef init do sounds.add self
end
redef class Sound
redef fun load do
if is_loaded then return
- var retval_resources = app.default_soundpool.load_name_rid(app.resource_manager, app.native_activity, self.name.strip_extension)
+ var retval_resources = app.default_soundpool.load_name_rid(app.resource_manager, app.native_activity, path.strip_extension)
if retval_resources == -1 then
- self.error = new Error("failed to load" + self.name)
- var nam = app.asset_manager.open_fd(self.name)
+ self.error = new Error("Failed to load " + path)
+ var nam = app.asset_manager.open_fd(path)
if nam.is_java_null then
- self.error = new Error("Failed to get file descriptor for " + self.name)
+ self.error = new Error("Failed to get file descriptor for " + path)
else
var retval_assets = app.default_soundpool.load_asset_fd_rid(nam)
if retval_assets == -1 then
- self.error = new Error("Failed to load" + self.name)
+ self.error = new Error("Failed to load " + path)
else
self.soundpool_id = retval_assets
self.soundpool = app.default_soundpool
self.soundpool.error = null
end
is_loaded = true
+
+ var error = error
+ if error != null then print_error error
end
redef fun play do
redef fun load do
if is_loaded then return
- var mp_sound_resources = app.default_mediaplayer.load_sound(app.resource_manager.raw_id(self.name.strip_extension), app.native_activity)
+ var mp_sound_resources = app.default_mediaplayer.load_sound(app.resource_manager.raw_id(path.strip_extension), app.native_activity)
if mp_sound_resources.error != null then
self.error = mp_sound_resources.error
- var nam = app.asset_manager.open_fd(self.name)
+ var nam = app.asset_manager.open_fd(path)
if nam.is_java_null then
- self.error = new Error("Failed to get file descriptor for " + self.name)
+ self.error = new Error("Failed to get file descriptor for " + path)
else
var mp_sound_assets = app.default_mediaplayer.data_source_fd(nam)
if mp_sound_assets.error != null then
self.media_player.error = null
end
is_loaded = true
+
+ var error = error
+ if error != null then print_error error
end
redef fun play do
App_native_activity(self).setVolumeControlStream(AudioManager.STREAM_MUSIC);
`}
- # Retrieves a sound with a soundpool in the `assets` folder using its name.
- # Used to play short songs, can play multiple sounds simultaneously
- redef fun load_sound(path) do
- var fd = asset_manager.open_fd(path)
- if not fd.is_java_null then
- return add_to_sounds(default_soundpool.load_asset_fd(fd)).as(Sound)
- else
- var error = new Error("Failed to load Sound {path}")
- return new Sound.priv_init(null, -1, default_soundpool, error)
- end
- end
-
- # Retrieves a music with a media player in the `assets` folder using its name.
- # Used to play long sounds or musics, can't play multiple sounds simultaneously
- redef fun load_music(path) do
- var fd = asset_manager.open_fd(path)
- if not fd.is_java_null then
- return add_to_sounds(default_mediaplayer.data_source_fd(fd)).as(Music)
- else
- var error = new Error("Failed to load music {path}")
- return new Music.priv_init(null, default_mediaplayer, error)
- end
- end
-
# Same as `load_sound` but load the sound from the `res/raw` folder
fun load_sound_from_res(sound_name: String): Sound do
- return add_to_sounds(default_soundpool.load_name(resource_manager,self.native_activity, sound_name)).as(Sound)
+ var sound = default_soundpool.load_name(resource_manager,self.native_activity, sound_name)
+ sys.sounds.add sound
+ return sound
end
# Same as `load_music` but load the sound from the `res/raw` folder
fun load_music_from_res(music: String): Music do
- return add_to_sounds(default_mediaplayer.load_sound(resource_manager.raw_id(music), self.native_activity)).as(Music)
+ var sound = default_mediaplayer.load_sound(resource_manager.raw_id(music), self.native_activity)
+ sys.sounds.add sound
+ return sound
end
redef fun on_pause do
# Sounds handled by the application, when you load a sound, it's added to this list.
# This array is used in `pause` and `resume`
private var sounds = new Array[PlayableAudio]
-
- # Factorizes `sounds.add` to use it in `load_music`, `load_sound`, `load_music_from_res` and `load_sound_from_res`
- private fun add_to_sounds(sound: PlayableAudio): PlayableAudio do
- sounds.add(sound)
- return sound
- end
end
# See the License for the specific language governing permissions and
# limitations under the License.
-# App audio abstraction
-# Default behaviour is loading the audio from the `assets` folder of the project with its name and extension
-# Platforms implementations can modify this comportement
+# Services to load and play `Sound` and `Music` from the assets folder
#
-# Once the application has started (after `App.setup`)
-# use `App.load_sound` to get a sound
-# then `Sound.play` to play it
+# Get a handle to a sound using `new Sound` or `new Music` at any time.
+# Call `load` at or after `App::on_create` or leave it to be loaded
+# on demand by the first call to `play`.
module audio
import app_base
# Abstraction of a playable Audio
abstract class PlayableAudio
- # Name of this playable audio
- var name: String
+ # Path to the audio file in the assets folder
+ var path: String
- # Error which is not null if an error happened
+ # Last error on this sound, if any
var error: nullable Error = null is writable
- # Is this already loaded ?
+ # Is `self` already loaded?
protected var is_loaded = false is writable
# Load this playable audio
fun load is abstract
- # Plays the sound
+ # Play the sound
fun play is abstract
- # Pauses the sound
+ # Pause the sound
fun pause is abstract
- # Resumes the sound
+ # Resume the sound
fun resume is abstract
end
-# Abstraction of a short sound
+# Short sound
class Sound
super PlayableAudio
end
-# Abstraction of a long song, that can bee looped
+# Long sound that can bee looped
class Music
super PlayableAudio
end
-
-redef class App
-
- # Load a sound
- fun load_sound(name: String): Sound is abstract
-
- # Load a music
- fun load_music(name: String): Music is abstract
-end
# assert deserializer.errors.is_empty # If false, `obj` is invalid
# print object
# ~~~
+#
+# ### Missing attributes and default values
+#
+# When reading JSON, some attributes expected by Nit classes may be missing.
+# The JSON object may come from an external API using optional attributes or
+# from a previous version of your program without the attributes.
+# When an attribute is not found, the deserialization engine acts in one of three ways:
+#
+# 1. If the attribute has a default value or if it is annotated by `lazy`,
+# the engine leave the attribute to the default value. No error is raised.
+# 2. If the static type of the attribute is nullable, the engine sets
+# the attribute to `null`. No error is raised.
+# 3. Otherwise, the engine raises an error and does not set the attribute.
+# The caller must check for `errors` and must not read from the attribute.
+#
+# ~~~nitish
+# import json::serialization
+#
+# class MyConfig
+# serialize
+#
+# var width: Int # Must be in JSON or an error is raised
+# var height = 4
+# var volume_level = 8 is lazy
+# var player_name: nullable String
+# var tmp_dir: nullable String = "/tmp" is lazy
+# end
+#
+# # ---
+# # JSON object with all expected attributes -> OK
+# var plain_json = """
+# {
+# "width": 11,
+# "height": 22,
+# "volume_level": 33,
+# "player_name": "Alice",
+# "tmp_dir": null
+# }"""
+# var deserializer = new JsonDeserializer(plain_json)
+# var obj = new MyConfig.from_deserializer(deserializer)
+#
+# assert deserializer.errors.is_empty
+# assert obj.width == 11
+# assert obj.height == 22
+# assert obj.volume_level == 33
+# assert obj.player_name == "Alice"
+# assert obj.tmp_dir == null
+#
+# # ---
+# # JSON object missing optional attributes -> OK
+# plain_json = """
+# {
+# "width": 11
+# }"""
+# deserializer = new JsonDeserializer(plain_json)
+# obj = new MyConfig.from_deserializer(deserializer)
+#
+# assert deserializer.errors.is_empty
+# assert obj.width == 11
+# assert obj.height == 4
+# assert obj.volume_level == 8
+# assert obj.player_name == null
+# assert obj.tmp_dir == "/tmp"
+#
+# # ---
+# # JSON object missing the mandatory attribute -> Error
+# plain_json = """
+# {
+# "player_name": "Bob",
+# }"""
+# deserializer = new JsonDeserializer(plain_json)
+# obj = new MyConfig.from_deserializer(deserializer)
+#
+# # There's an error, `obj` is partial
+# assert deserializer.errors.length == 1
+#
+# # Still, we can access valid attributes
+# assert obj.player_name == "Bob"
+# ~~~
module serialization
import ::serialization::caching
if not root isa Error then
errors.add new Error("Deserialization Error: parsed JSON value is not an object.")
end
+ deserialize_attribute_missing = false
return null
end
var current = path.last
if not current.keys.has(name) then
- errors.add new Error("Deserialization Error: JSON object has not attribute '{name}'.")
+ # Let the generated code / caller of `deserialize_attribute` raise the missing attribute error
+ deserialize_attribute_missing = true
return null
end
attributes_path.add name
var res = convert_object(value, static_type)
attributes_path.pop
+
+ deserialize_attribute_missing = false
return res
end
# See the License for the specific language governing permissions and
# limitations under the License.
-# Linux audio services
+# Linux audio implementation
module audio
import app::audio
import linux
-# Simple audio asset
redef class Sound
redef fun play do
- if name.has_suffix(".wav") then
- sys.system "aplay -q {app.assets_dir}{name} &"
- else if name.has_suffix(".mp3") then
- sys.system "mpg123 -q {app.assets_dir}{name} &"
+ if path.has_suffix(".wav") then
+ sys.system "aplay -q {app.assets_dir}{path} &"
+ else if path.has_suffix(".mp3") then
+ sys.system "mpg123 -q {app.assets_dir}{path} &"
end
end
redef class Music
redef fun play do
- if name.has_suffix(".wav") then
- sys.system "aplay -q {app.assets_dir}{name} &"
- else if name.has_suffix(".mp3") then
- sys.system "mpg123 -q {app.assets_dir}{name} &"
+ if path.has_suffix(".wav") then
+ sys.system "aplay -q {app.assets_dir}{path} &"
+ else if path.has_suffix(".mp3") then
+ sys.system "mpg123 -q {app.assets_dir}{path} &"
end
end
redef fun pause do end
redef fun resume do end
end
-
-redef class App
- redef fun load_sound(name)
- do
- return new Sound(name)
- end
-
- redef fun load_music(name)
- do
- return new Music(name)
- end
-end
# The `static_type` can be used as last resort if the deserialized object
# desn't have any metadata declaring the dynamic type.
#
+ # Return the deserialized value or null on error, and set
+ # `deserialize_attribute_missing` to whether the attribute was missing.
+ #
# Internal method to be implemented by the engines.
fun deserialize_attribute(name: String, static_type: nullable String): nullable Object is abstract
+ # Was the attribute queried by the last call to `deserialize_attribute` missing?
+ var deserialize_attribute_missing = false
+
# Register a newly allocated object (even if not completely built)
#
# Internal method called by objects in creation, to be implemented by the engines.
-<ul class='nav nav-tabs' role='tablist'>
+<ul class='nav nav-tabs' ng-init='entityCtrl.loadEntityLinearization()'>
<li role='presentation' class='warning'>
<a ng-href='{{mentity.mclass.web_url}}'>
<span class='glyphicon glyphicon-chevron-left'/> Go to class
-<ul class='nav nav-tabs'>
+<ul class='nav nav-tabs' ng-init='entityCtrl.loadEntityLinearization()'>
<li role='presentation' class='warning'>
<a href='{{mentity.mproperty.web_url}}'>
<span class='glyphicon glyphicon-chevron-left'/> Go to property
code.add """
self.{{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}", "{{{type_name}}}")
"""
- else code.add """
+ else
+ code.add """
var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}", "{{{type_name}}}")
- if not {{{name}}} isa {{{type_name}}} then
- # Check if it was a subjectent error
- v.errors.add new AttributeTypeError(self, "{{{attribute.serialize_name}}}", {{{name}}}, "{{{type_name}}}")
+ if v.deserialize_attribute_missing then
+"""
+ # What to do when an attribute is missing?
+ if attribute.has_value then
+ # Leave it to the default value
+ else if mtype isa MNullableType then
+ code.add """
+ self.{{{name}}} = null"""
+ else code.add """
+ v.errors.add new Error("Deserialization Error: attribute `{class_name}::{{{name}}}` missing from JSON object")"""
- # Clear subjacent error
+ code.add """
+ else if not {{{name}}} isa {{{type_name}}} then
+ v.errors.add new AttributeTypeError(self, "{{{attribute.serialize_name}}}", {{{name}}}, "{{{type_name}}}")
if v.keep_going == false then return
else
self.{{{name}}} = {{{name}}}
end
"""
+ end
end
code.add "end"
# Return `self` as a JsonObject.
#
- # By default, every reference to another MEntity is replaced by a pointer
- # to the MEntity::json_id.
+ # By default, every reference to another MEntity is skipped.
+ # Use full_json for a full representation.
fun json: JsonObject do
var obj = new JsonObject
obj["name"] = name
obj["full_name"] = full_name
obj["mdoc"] = mdoc_or_fallback
obj["visibility"] = visibility
- obj["location"] = location
var modifiers = new JsonArray
for modifier in collect_modifiers do
modifiers.add modifier
end
redef fun to_json do return json.to_json
+
+ # Return `self` as a JsonObject with references.
+ #
+ # By default, every reference to another MEntity is replaced by a pointer
+ # to the MEntity::json_id.
+ fun full_json: JsonObject do
+ var obj = json
+ obj["location"] = location
+ return obj
+ end
+
+ # Return `full_json` as a json string.
+ fun to_full_json: String do return full_json.to_json
end
redef class MDoc
redef class MPackage
- redef fun json do
+ redef fun full_json do
var obj = super
if ini != null then
obj["ini"] = new JsonObject.from(ini.as(not null).to_map)
redef fun json do
var obj = super
obj["is_root"] = is_root
+ return obj
+ end
+
+ redef fun full_json do
+ var obj = super
obj["mpackage"] = to_mentity_ref(mpackage)
obj["default_mmodule"] = to_mentity_ref(default_mmodule)
obj["parent"] = to_mentity_ref(parent)
end
redef class MModule
- redef fun json do
+ redef fun full_json do
var obj = super
obj["mpackage"] = to_mentity_ref(mpackage)
obj["mgroup"] = to_mentity_ref(mgroup)
obj["intro_mclasses"] = to_mentity_refs(intro_mclasses)
obj["mclassdefs"] = to_mentity_refs(mclassdefs)
+ obj["intro_mclassdefs"] = to_mentity_refs(collect_intro_mclassdefs(private_view))
+ obj["redef_mclassdefs"] = to_mentity_refs(collect_redef_mclassdefs(private_view))
+ obj["imports"] = to_mentity_refs(in_importation.direct_greaters)
return obj
end
end
redef class MClass
redef fun json do
var obj = super
- var arr = new JsonArray
- for mparameter in mparameters do arr.add mparameter
- obj["mparameters"] = arr
+ obj["mparameters"] = new JsonArray.from(mparameters)
+ return obj
+ end
+
+ redef fun full_json do
+ var obj = super
obj["intro"] = to_mentity_ref(intro)
obj["intro_mmodule"] = to_mentity_ref(intro_mmodule)
obj["mpackage"] = to_mentity_ref(intro_mmodule.mpackage)
obj["mclassdefs"] = to_mentity_refs(mclassdefs)
+ obj["all_mproperties"] = to_mentity_refs(collect_accessible_mproperties(private_view))
+ obj["intro_mproperties"] = to_mentity_refs(collect_intro_mproperties(private_view))
+ obj["redef_mproperties"] = to_mentity_refs(collect_redef_mproperties(private_view))
+ obj["parents"] = to_mentity_refs(collect_parents(private_view))
return obj
end
end
redef fun json do
var obj = super
obj["is_intro"] = is_intro
- var arr = new JsonArray
- for mparameter in mclass.mparameters do arr.add mparameter
- obj["mparameters"] = arr
+ obj["mparameters"] = new JsonArray.from(mclass.mparameters)
+ return obj
+ end
+
+ redef fun full_json do
+ var obj = super
obj["mmodule"] = to_mentity_ref(mmodule)
obj["mclass"] = to_mentity_ref(mclass)
obj["mpropdefs"] = to_mentity_refs(mpropdefs)
obj["intro_mproperties"] = to_mentity_refs(intro_mproperties)
+ obj["intro"] = to_mentity_ref(mclass.intro)
+ obj["mpackage"] = to_mentity_ref(mmodule.mpackage)
+ obj["intro_mpropdefs"] = to_mentity_refs(collect_intro_mpropdefs(private_view))
+ obj["redef_mpropdefs"] = to_mentity_refs(collect_redef_mpropdefs(private_view))
return obj
end
end
redef class MProperty
- redef fun json do
+ redef fun full_json do
var obj = super
obj["intro"] = to_mentity_ref(intro)
obj["intro_mclassdef"] = to_mentity_ref(intro_mclassdef)
obj["mpropdefs"] = to_mentity_refs(mpropdefs)
+ obj["intro_mclass"] = to_mentity_ref(intro_mclassdef.mclass)
+ obj["mpackage"] = to_mentity_ref(intro_mclassdef.mmodule.mpackage)
return obj
end
end
redef fun json do
var obj = super
obj["is_intro"] = is_intro
+ return obj
+ end
+
+ redef fun full_json do
+ var obj = super
obj["mclassdef"] = to_mentity_ref(mclassdef)
obj["mproperty"] = to_mentity_ref(mproperty)
+ obj["intro"] = to_mentity_ref(mproperty.intro)
+ obj["intro_mclassdef"] = to_mentity_ref(mproperty.intro.mclassdef)
+ obj["mmodule"] = to_mentity_ref(mclassdef.mmodule)
+ obj["mgroup"] = to_mentity_ref(mclassdef.mmodule.mgroup)
+ obj["mpackage"] = to_mentity_ref(mclassdef.mmodule.mpackage)
return obj
end
end
# Is the node tagged optional?
var is_optional = false
- # Has the node a default value?
- # Could be through `n_expr` or `n_block`
+ # Does the node have a default value?
+ # Could be through `n_expr`, `n_block` or `is_lazy`
var has_value = false
# The guard associated to a lazy attribute.
end
# Check that `sub` is a subtype of `sup`.
- # If `sub` is not a valid suptype, then display an error on `node` an return null.
- # If `sub` is a safe subtype of `sup` then return `sub`.
- # If `sub` is an unsafe subtype (ie an implicit cast is required), then return `sup`.
+ # If `sub` is not a valid suptype, then display an error on `node` and return `null`.
+ # If `sub` is a safe subtype of `sup`, then return `sub`.
+ # If `sub` is an unsafe subtype (i.e., an implicit cast is required), then return `sup`.
#
# The point of the return type is to determinate the usable type on an expression when `autocast` is true:
# If the suptype is safe, then the return type is the one on the expression typed by `sub`.
res.api_error(404, "No linearization for mentity `{mentity.full_name}`")
return
end
- res.json new JsonArray.from(lin)
+ var mentities = new JsonArray
+ for e in lin do mentities.add e.full_json
+ res.json mentities
end
end
end
# Get the full json repesentation of `self` with MEntityRefs resolved.
- fun api_json(handler: APIHandler): JsonObject do return json
+ fun api_json(handler: APIHandler): JsonObject do return full_json
end
redef class MEntityRef
obj["name"] = mentity.name
obj["mdoc"] = mentity.mdoc_or_fallback
obj["visibility"] = mentity.visibility
- obj["location"] = mentity.location
var modifiers = new JsonArray
for modifier in mentity.collect_modifiers do
modifiers.add modifier
end
return obj
end
-end
-
-redef class MDoc
-
- # Add doc down processing
- redef fun json do
- var obj = super
- obj["synopsis"] = synopsis
- obj["documentation"] = documentation
- obj["comment"] = comment
- obj["html_synopsis"] = html_synopsis.write_to_string
- obj["html_documentation"] = html_documentation.write_to_string
- obj["html_comment"] = html_comment.write_to_string
- return obj
- end
-end
-
-redef class MModule
- redef fun api_json(handler) do
- var obj = super
- obj["intro_mclassdefs"] = to_mentity_refs(collect_intro_mclassdefs(private_view))
- obj["redef_mclassdefs"] = to_mentity_refs(collect_redef_mclassdefs(private_view))
- obj["imports"] = to_mentity_refs(in_importation.direct_greaters)
- return obj
- end
-end
-
-redef class MClass
- redef fun api_json(handler) do
- var obj = super
- obj["all_mproperties"] = to_mentity_refs(collect_accessible_mproperties(private_view))
- obj["intro_mproperties"] = to_mentity_refs(collect_intro_mproperties(private_view))
- obj["redef_mproperties"] = to_mentity_refs(collect_redef_mproperties(private_view))
- obj["parents"] = to_mentity_refs(collect_parents(private_view))
- return obj
- end
-end
-
-redef class MClassDef
- redef fun json do
- var obj = super
- obj["intro"] = to_mentity_ref(mclass.intro)
- obj["mpackage"] = to_mentity_ref(mmodule.mpackage)
- return obj
- end
- redef fun api_json(handler) do
+ redef fun full_json do
var obj = super
- obj["intro_mpropdefs"] = to_mentity_refs(collect_intro_mpropdefs(private_view))
- obj["redef_mpropdefs"] = to_mentity_refs(collect_redef_mpropdefs(private_view))
+ obj["location"] = mentity.location
return obj
end
end
-redef class MProperty
- redef fun json do
- var obj = super
- obj["intro_mclass"] = to_mentity_ref(intro_mclassdef.mclass)
- obj["mpackage"] = to_mentity_ref(intro_mclassdef.mmodule.mpackage)
- return obj
- end
-end
+redef class MDoc
-redef class MPropDef
+ # Add doc down processing
redef fun json do
- var obj = super
- obj["intro"] = to_mentity_ref(mproperty.intro)
- obj["intro_mclassdef"] = to_mentity_ref(mproperty.intro.mclassdef)
- obj["mmodule"] = to_mentity_ref(mclassdef.mmodule)
- obj["mgroup"] = to_mentity_ref(mclassdef.mmodule.mgroup)
- obj["mpackage"] = to_mentity_ref(mclassdef.mmodule.mpackage)
+ var obj = new JsonObject
+ obj["html_synopsis"] = html_synopsis.write_to_string
+ obj["html_documentation"] = html_documentation.write_to_string
return obj
end
end
Runtime error: Uninitialized attribute _s (alt/test_json_deserialization_plain_alt2.nit:27)
# JSON: {"__class": "MyClass", "i": 123, "o": null}
-# Errors: 'Deserialization Error: JSON object has not attribute 's'.', 'Deserialization Error: Wrong type on `MyClass::s` expected `String`, got `null`', 'Deserialization Error: JSON object has not attribute 'f'.', 'Deserialization Error: Wrong type on `MyClass::f` expected `Float`, got `null`', 'Deserialization Error: JSON object has not attribute 'a'.', 'Deserialization Error: Wrong type on `MyClass::a` expected `Array[String]`, got `null`'
+# Errors: 'Deserialization Error: attribute `MyClass::s` missing from JSON object', 'Deserialization Error: attribute `MyClass::f` missing from JSON object', 'Deserialization Error: attribute `MyClass::a` missing from JSON object'
# Nit: <MyClass i:123 s:hello f:123.456 a:[one, two] o:<null>>
# JSON: {"__class": "MyClass", "i": 123, "s": "hello", "f": 123.456, "a": ["one", "two"]}
-# Errors: 'Deserialization Error: JSON object has not attribute 'o'.'
# Nit: <MyClass i:123 s:hello f:123.456 a:[one, two] o:<null>>
# JSON: {"__class": "MyClass", "i": 123, "s": "hello", "f": 123.456, "a": ["one", "two"], "o":
Runtime error: Uninitialized attribute _s (alt/test_json_deserialization_plain_alt2.nit:22)
# JSON: {"__class": "MyClass", "i": 123, "o": null}
-# Errors: 'Deserialization Error: JSON object has not attribute 's'.', 'Deserialization Error: Wrong type on `MyClass::s` expected `String`, got `null`', 'Deserialization Error: JSON object has not attribute 'f'.', 'Deserialization Error: Wrong type on `MyClass::f` expected `Float`, got `null`', 'Deserialization Error: JSON object has not attribute 'a'.', 'Deserialization Error: Wrong type on `MyClass::a` expected `Array[String]`, got `null`'
+# Errors: 'Deserialization Error: attribute `MyClass::s` missing from JSON object', 'Deserialization Error: attribute `MyClass::f` missing from JSON object', 'Deserialization Error: attribute `MyClass::a` missing from JSON object'
tests.add """
{"__class": "MyClass", "i": 123, "s": "hello", "f": 123.456, "o": null, "a": ["one", "two"], "Some random attribute": 777}"""
-# Skipping `o` will cause an error but the attribute will be set to `null`
+# Skipping `o` will set the attribute to `null`
tests.add """
{"__class": "MyClass", "i": 123, "s": "hello", "f": 123.456, "a": ["one", "two"]}"""