Quick: MicroObjects and Json

A question on twitter has moved forward doing an example of serialization and deserialization. This is gonna be a pretty quick example and blog post as this is being done so that I can provide a code sample as my answer to the asker!

I'm going to use JSON as the example serialization/deserialization. The basic idea of how to deserialize is... not.

Seriously, don't deserialize. Why do we need to extract the data into fields in some object? To use them? pfft, we can extract it later, when it's actually required. One of the cool things I've found working with microobejcts is the delayed execution. We don't actually perform any data manipulation (or retrieval in some cases) until and unless that data is needed.

Let's use Hacker News as an example. I'm using this as it'll be an excellent comparrison to see how my view on deserialization should happen.
Let's compare to what I did... quite a while ago. Doing many things I won't do now. More, doing many things I don't need to do now. Avoiding these practices allows my code to be much simpler and much easier to maintain.
My HackerNewsReader was last touched in in April 2017. Writing now it's July 2018. Well over a year ago - so many different practices in place now. Amazing growth for me.

I'm not going to do Java, I'm aiming for quick on this, I don't want to fight tools I haven't spun up in a while. :)

Deserialization

First thing we'll look at is how to deserialize the JSON. This is also an example for data from a database, or the DataBag back from the ORM system - looking at you Entity Framework. I focus on JSON as it's slightly more complex example.

... OK, looking at the HackerNews API... I'm not going to use it. It's lacking some of the 'interrogation' ability that I want to demonstrate.

I'm going to create a fake user json... which I thought I had ... in some project... somewhere... OH, I know I did... but I blew it all away when I screwed up the example.

Fine - new fancy pants JSON for my simple example

{
    "name":"Quinn Fyzxs",
    "birthday":"19061209"
    "contact_info":[
        {
            "type":"phone",
            "value":"5551234567"
        }
    ]
}

There - simple json. Not sure if it'll serve my purpose, but let's figure it out. I get to cheat a bit and use some of my MicroObjectsCommon classes for interacting with JSON, found in the json namespace as a foundation. I'm stripping it to hell and back though - a lot we don't need quite yet.
hmmm.... Nope. Screw it. I'm pulling the whole thing in. I'm going for quick, not "MINIMAL" - so pulling in my MicroObjectsLib and FluentTypes. These are my foundation of microobjects that allows me to develop quickly. Feel free to use them, or build your own. I advocate having the source to always be able to make them exactly what you need.

Anyway... Now I have my JsonObjectBookEnd, I can begin.

I have the above JSON, what do I want to do with it? The simplest for this example, and why I built custom, is to ask questions about the age.

The core of what we do is to use our JsonObjectBookEnd to control the interaction with the RAW DATA. Raw data is a code smell. If you see it, something's probably not as Object Oriented as it can be.

Our skeleton of a User class looks like

public class User : IUser
{
    private readonly IJsonObjectBookEnd _source;

    public User(Text json) : this(new JsonObjectBookEnd(json)) { }

    private User(IJsonObjectBookEnd source) => _source = source;
}

Let's ask a question about the age of the user... IsMinor?
I'm going to shortcut the evolution of the code and jump straight to what I would show - If I had a lot of methods on User for the age, our User class would know a lot about how to answer age questions. Why? The User knows nothing of Age, it just knows the users birthday. Let's keep objects as ignorant as possible. "I know what I want, you know how to do it". Let's keep it like that.
I'd normally implement IsMinor, CanVote, CanBuyAlcohol on User then move them into an Age object. Gonna skip the second two and go straight to Age with isMinor.

... I apologize for all the 'await's. These support classes were developed in a a very async environment and continue to be used in such. I haven't created non-async/await versions.

Now I have an Age class that looks like

public sealed class Age : IAge
{
    private readonly TimeInstant _birthday;

    public Age(Text birthday) : this(new YearMonthDayTimeInstant(birthday)) { }

    private Age(TimeInstant birthday) => _birthday = birthday;

    public Bool IsMinor()
    {
        return Bool.False;
    }
}

and my User is

public sealed class User : IUser
{
    private readonly IJsonObjectBookEnd _source;
    private static readonly Text BirthdayKey = new TextOf("birthday");

    public User(Text json) : this(new JsonObjectBookEnd(json)) { }

    private User(IJsonObjectBookEnd source) => _source = source;

    public async Task<IAge> Age() => new Age(await _source.TextValue(BirthdayKey));
}

I'm not bothering with the actual date manipulations, that'll come in later examples... mostly because I don't have my Time foundation classes in the repo yet...

Depending on how much I want to drive certain things, I can ask more advanced questions. Right now, not gonna push on that. Showing the deserialization practice.

We can ask questions about the age. Sweet. What about the other info?

We follow the same pattern. I want to do something with the name: I need a Name object, which the User knows how to construct. Remember: I know what I want, you know how to do it.

public sealed class User : IUser
{
    private static readonly Text BirthdayKey = new TextOf("birthday");
    private static readonly Text NameKey = new TextOf("name");

    private readonly IJsonObjectBookEnd _source;

    public User(Text json) : this(new JsonObjectBookEnd(json)) { }

    private User(IJsonObjectBookEnd source) => _source = source;

    public async Task<IAge> Age() => new Age(await _source.TextValue(BirthdayKey));
    public async Task<IName> Name() => new Name(await _source.TextValue(NameKey));
}

public sealed class Name : IName
{
    private readonly Text _origin;

    public Name(Text origin) => _origin = origin;
}

We can now ask questions of the name object... like... uhh... Normally this just gets the "into/writer" technique to display it...

That's enough

OK. For a quick post, I think we're demonstrating what I do to deserialize - I create objects representing the concepts of the payload. I can then ask these objects questions. I don't need databags of the data.
I'm not currently taking advantage of the system and some auto-parsing - but with my practice of avoiding the asymetric marriage to my JSON library, I'm not seeing a negative impact to the code.

Show Comments