Skip to content

olichose123/olib_model

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

32 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

olib_model - storing classes as json by reference

  • Using the wonderful json2object library to parse and write models to json.
  • Using utest for testing.

This library allows for the serializing and deserializing of models, sometimes called definitions, and references to other models.

Why? Because sometimes you want to store objects as reference instead of nested json. And often, you don't want to manually deserialize your data to take this into account. This means not having to separate objects from their references, not having to manually build objects from json, then find the references. This library does it for you.

Models are stored by type and name in a dictionary. References to models are converted to string during serialization and back to references during deserialization.

It's better to show with examples.

class Person extends olib.models.Model
{
    // public static final Type:String = "Person"; is auto-generated based on class name
    // public final type:String; is auto-generated, takes the value of static Type
    // public final name:String; is auto-generated
    public var age:Int;
    public var dog:Reference<Dog>;
}

class Dog extends olib.models.Model
{
    public var age:Int;
}

// create a person
var georges = new Person("Georges", 42, "charlie"); // optional arguments are auto-generated.
// name is not optional
// the dog charlie does not exist yet; but a reference to it now exists

Model.get("Person", "Georges") == georges;
Model.get(Person.Type, "Georges") == georges;
Model.get(georges.type, "Georges") == georges;

// create a dog
georges.dog.get() == null;
var charlie = new Dog("charlie", 3);
georges.dog.get() == charlie;

// serialize georges using auto json2object writer field
var georges_json = Person.writer.write(georges);
// georges_json == {"type":"Person","name":"Georges","age":42,"dog":"charlie"}
// note that charlie is not serialized, only referenced

var charlie_json = Dog.writer.write(charlie);
// charlie_json == {"type":"Dog","name":"charlie","age":3}

// deserialize georges
georges = Person.parser.fromJson(georges_json);
georges == Model.get("Person", "Georges");

// deserialize charlie
charlie = Dog.parser.fromJson(charlie_json);
charlie == Model.get("Dog", "charlie");

Installation

haxelib install olib_model

Usage

All models must extend olib.models.Model. By doing this, those models gain special abilities from build macros.

Models have a name:String field that cannot be changed after instantiation. This is the unique key to reference them by.

Models also have a type:String field that cannot be changed after instantiation. This is the type of the model. It takes the value of the class' name, so a class Person will have a type of "Person". It is also stored as a static Type field. The Type of a model can instead be manually set by declaring a metadata value with a different type name. In the following example, the Type of Person is "Human" instead of "Person".

@customTypeName("Human")
class Person extends olib.models.Model
{
    // public static final Type:String = "Human"; auto-generated, defaults to metadata value instead of class name
    // public final type:String = "Human"; auto-generated, same as static Type
    // public final name:String; auto-generated
    public var age:Int;
}

By default, when you instantiate two models of the same type and name, the second one overrides the first. You can change this behavior globally:

Model.duplicateHandling = DuplicateHandling.Overwrite; // will overwrite the current model
Model.duplicateHandling = DuplicateHandling.Error; // will throw a ModelException
Model.duplicateHandling = DuplicateHandling.Ignore;	// will ignore the new model

You can also overwrite the behavior per model. This is useful if you want to block a model type from being overwritten, for example:

@duplicateHandling("Error")
class ImportantGameSetings extends Model
{
   // you wouldn't want a mod to overwrite your Settings model, so you can disable it here
}

Models have json2object parsers and writers as static fields. These are generated by build macros. You can use them to serialize and deserialize models. Have a read at the json2object documentation for more information.

var georges = new Person("Georges", 42);
var georges_json = Person.writer.write(georges);
var georges = Person.parser.fromJson(georges_json);

You can peek a json's type with Model.peek(json_data). This will attempt to find a "type": "value" and return "value". This is useful to determine the type of a serialized model before attempting to serialize it.

// load json from file
var json = sys.io.File.getContent("path/to/file.json");
// peek the type
var type = Model.peek(json);
switch (type)
{
    case Person.Type:
        var person = Person.parser.fromJson(json);
    case _:
        trace("unknown type");
}

References

A Reference is a special abstract type that is stored as a string in json format. It represents a named model. The Type Parameter is used to determine the type. For example, by creating var person:Reference<Person> = "Georges";, the variable person has the value "Georges", but also has a special method get():Person that returns the Person named "Georges" if it exists, or null otherwise. When serialized, the json takes the form of "person":"Georges". When deserialized, the json is converted to a Reference<Person> with the value "Georges".

You can use References in arrays with the form Array<Reference<Person>> or in other structures.

It is important to note that if you store a model within a model without using a Reference, the json output will be a nested json of the parent and child models. In other words:

class Dog extends olib.models.Model
{
    public var age:Int;
}

var charlie = new Dog("charlie", 3);

class Person extends olib.models.Model
{
    public var name:String;
    public var age:Int;
    public var dog:Dog;
}

// output_json
'
{
    "type":"Person",
    "name":"Georges",
    "age":42,
    "dog":
    {
        "type":"Dog",
        "name":"charlie",
        "age":3
    }
}
'

class Person extends olib.models.Model
{
    public var name:String;
    public var age:Int;
    public var dog:Reference<Dog>;
}

// output_json
'
{
    "type":"Person",
    "name":"Georges",
    "age":42,
    "dog":"charlie"
}
'

What else?

You can use models to store other models in a list. For example, without using a reference, you can have an array of models instead of a model per file.

class PersonList extends olib.models.Model
{
    public var persons:Array<Person>;
}

// example json
'
{
    "type":"PersonList",
    "persons":
    [
        {
            ...
        },
        {
            ...
        }
    ]
}
'

Note that models are auto-added to the master dictionary Model.all when created or when deserialized.

You can have complex dependencies. A Starship can have a list of Weapons as references, and each Weapon has a list of values, but also a specific Projectile model as a reference.

class Starship extends olib.models.Model
{
    public var name:String;
    public var weapons:Array<Reference<Weapon>>;
}

class Weapon extends olib.models.Model
{
    public var name:String;
    public var projectile:Reference<Projectile>;
    public var values:Array<Int>;
}

class Projectile extends olib.models.Model
{
    public var name:String;
    public var damage:Int;
}

Remember to use json2object's @:jignored metadata attribute to ignore fields that you don't want to serialize. For example, you may want to ignore the values field in the Weapon model.

class Weapon extends olib.models.Model
{
    public var name:String;
    public var projectile:Reference<Projectile>;
    @:jignored public var values:Array<Int>;
}
// output_json
'
{
    "type":"Weapon",
    "name":"laser",
    "projectile":"laser",
}
'

About

Haxe models for your every needs

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages