(de)serializing c++ object almost automagically
Avoid creating namespace aliases in header files
I assume that at least the declaration of class properties would be put in a header file. In that case, consider that users of that header file might not expect namespace nlm to be declared, so I recommend just writing out nlohmann fully.
Spelling
A minor issue: it is serializer, not serializor.
Function names
Avoid overloading state() to mean either setting the state or getting the state. While related, these are different operations, and it is much better to make that explicit by giving them different function names. An obvious modification is to name them get_state() and set_state(), but that sounds quite generic. I would also make it explicit that you are converting to or from JSON, so consider naming them to_json() and from_json().
Overhead
Your serialization method introduces a huge overhead. Every instance of a serializable struct now has to contain a std::unordered_map, which is filled in in the constructor. So this costs time and memory. It would be much nicer if you could build this only once per type that derives from properties. Perhaps it can be done using static variables and CRTP, something like:
template&lt;typename T&gt;
struct properties
{
    struct registry
    {
        // keeps the actual mapping
        ...
    };
    
    template &lt;typename U&gt;
    void set(str::string_view const&amp; k, U&amp;&amp; v) {
        // forward it to the registry object, along with a pointer to the object
        auto self = static_cast&lt;T *&gt;(this);
        self-&gt;registry.set(self, k, v);
    }

    ...
};

struct S: properties&lt;S&gt;
{
    bool b{};
    int i{};
    
    static properties::registry reg_;

public:
    ...
};

S::properties::registry S::reg_ = {{&quot;b&quot;, &amp;S::b}, {&quot;i&quot;, &amp;S::i}};

But I struggle myself with how to create a constructor for properties::registry that would allow the above code (especially the last line) to work.
Make get() const
You should make the get() member function const, as it should not modify the state, and this will allow that functions to be used on const instances of classes that inherit from properties.
Use range-for where possible
You can simplify the code in some places by using range-for. For example, in properties::state(), where you can also combine it with structured binding:
for (auto &amp;[name, variable]: reg_)
{
    r.emplace(name, variable.serializer());
}

It is unfortunate that the iterator of nlm::json doesn't work the same way; you can only access the value in a range-for, not the key.
Crash at runtime when accessing a non-existing property
If in main(), you call s.get(&quot;x&quot;), the program crashes with a segmentation fault. Even if you never expect this function to be called with a user-supplied name, it still makes it hard to debug programming errors. Check the return value of calls to find() before trying to dereference the result. You could throw a std::runtime_error if find() return nulltpr, or if you don't want to use exceptions or pay for the performance cost in production builds, at least use assert() to help with debug builds.