menu sluiten
Contact

Amsterdam
Pedro de Medinalaan 87a/b, 1086 XP Amsterdam
Nederland +31 (0)85-888 33 31

Breda
Neerloopweg 36, 4814 RS Breda
Nederland +31 (0)85-888 33 31

Antwerpen
Veldkant 33B, 2550 Kontich
België +32 (0)3 444 11 08

info@jstack.eu

21 april 2016

Using functional lenses to handle complex JSON structures in JavaScript

The problem

Handeling complex JSON structures can get tricky in bigger applications. Especially when some properties have sub properties you need, but the parent property is optional.
A quick example can illustrate this problem.

1
2
3
4
5
6
7
8
9
10
{
    "name": "John Doe",
    "date-of-birth": "2000-12-01",
    "email": "j.doe@provider.com",
    "address": {
        "street": "Main",
        "houseNumber": "3454",
        "bus": "2b"
    } 
}

This might be a normal looking JSON describing a person. In the database you might, or might not have the address, it is not mandatory therefore when getting the street name, we will have to check if the address exists first.

1
2
3
4
5
6
7
const john = { /* see above */ };
const address = john.address;

let street = undefined;
if (address) {
    street = address.street;
}

This is already a bit shaky implementation, you have to use a let while you know that constants are better… You have a if that looks a tad ugly… But try to imagine what would happen if we have more complex JSON structures that have complex optional properties in optional properties?

1
2
3
4
5
6
7
8
9
{
    "optional1": {
        "optional2": {
            "optional3": {
                "gold": "I know you want me!"
            }
        }
    }
}

How would we tackle this?

1
2
3
4
5
6
7
8
9
10
11
12
13
const obj = { /* see above */ };
const optional1 = obj.optional1;

let gold = undefined;
if (optional1) {
    let optional2 = optional1.optional2;
    if (optional2) {
        let optional3 = optional2.optional3;
        if (optional3) {
            gold = optional3.gold;
        }
    }
}

Now this looks completely silly… what would happen if you go 100 layers deep, you know, sure, that will never happen… until it does.

Bringing in lenses

A lens is a different way of looking at something. You are able to peek, set, or mutate the spot the lens is pointing to. It doesn’t matter what that spot is, a primitive value (int, string, boolean) or another complex object. If one of the objects in the lens is optional and not present, when looking at the value it will fail gracefully. If you would want to set that item, the lens will create every parent item that is not there. Now that’s cool!

In the following example we will be using the lensPath function from the ramda framework (http://ramdajs.com/docs/#lensPath);

1
2
3
4
5
6
7
8
9
const john = { /* see above */ };

const streetLens = R.lensPath(['address', 'street']);
const numberLens = R.lensPath(['address', 'houseNumber']);
const busLens = R.lensPath(['address', 'bus']);
const nameLens = R.lensPath(['address', 'bus']);

const street = R.view(streetLens, john);
const street = R.set(streetLens, 'First', john);

This looks a lot cleaner, no more let-statements and scales very well when the optional parameters are getting sky high. Note that we could also pass along parameters to our lens, making it a function that needs input.

1
2
3
4
const obj = { /* with values */ };
const abstractLens = (property) => R.lensPath(['a', property, 'c']);

const value = R.view(abstractLens('b'), obj);

Making it even better with a wrapper

If you have a lot of lenses working on the same object, it might be useful to create a scope for that object that is able to created lenses for it. We can easily add default values to the mix as well!

1
2
3
4
5
6
7
const john = { /* see john above */ };
const lensCreator = lensScopeFor(john);

const street = lensCreator(['address', 'street']);

const value = street.view('Default value');
const newJohn = street.set('First');

I thought about this wrapper, if it was making state that could be bad in the long run, but I couldn’t find any problem with it. The state (john) is already there and instead of using it all over the place, we are only allowing this state in the lensScope. In my opinion this is a big advantage.

Conclusion

A lens is a powerful tool to fight the if-statement hell we tend to encounter when working with complex JSON object that contain a lot of optional properties. We could be defining models (object-oriented) to capture and validate the input, but then we are just duplicating back-end’s code. The use of lenses allows us to peek into these models in a fail-safe environment so we do not need to lose time on validating matters.

If you are interested in the lensCreator code, I have excluded the code from this blogpost, try to figure it out yourself, it’s not so hard and your understanding of lenses will increase. If however you can’t wait, leave a comment (and email) and I’ll send you the code.

Meer weten?

Neem contact op met Kevin. Hij denkt graag met u mee in een persoonlijk gesprek.

info@jstack.eu +31 (0)85-888 33 31

Interessant? Deel dit artikel met een vriend(in) of collega!

Vragen over dit onderwerp?

Onze experts denken graag met u mee


Gerelateerde berichten