Implement custom serializer in way of JSON::Serializable

I’m trying to implement numbers and bytes fields -only objects encoding to compact binary format and at first stuck with @type.instance_vars not working before fields initialization (for what I thought it could be used):

module Encodable
  macro included
    def initialize(bytes : Bytes)
      {% for v in @type.instance_vars %} # `TypeNode#instance_vars` cannot be called in the top-level scope: instance vars are not yet initialized
          {% if v.id == UInt32.id %}
              @{{v.name.id}} = 0_u32
          {% elif v.id == Bytes.id %}
              @{{v.name.id}} = Bytes.new 0
          {% end %}
      {% end %}
    end
  end
end

struct Example
  include Encodable

  getter i : UInt32
  getter b : Bytes

  def initialize(@i, @b)
  end
end

puts Example.new Bytes.new 0

However, it is used in JSON::Serializable implementation, so I tried copying details like verbatim and new, although having no idea how it can help:

module Encodable
  macro included
    def self.new(bytes : Bytes)
      new_from_bytes(pull)
    end

    private def self.new_from_bytes(bytes : Bytes)
      instance = allocate
      instance.initialize bytes
      ::GC.add_finalizer(instance) if instance.responds_to?(:finalize)
      instance
    end

    def initialize(bytes : Bytes)
      {% verbatim do %}
        {% for v in @type.instance_vars %}
            {% if v.id == UInt32.id %}
                @{{v.name.id}} = 0_u32
            {% elif v.id == Bytes.id %}
                @{{v.name.id}} = Bytes.new 0
            {% end %}
        {% end %}
      {% end %}
    end
  end
end

struct Example
  include Encodable

  getter i : UInt32
  getter b : Bytes

  def initialize(@i, @b)
  end
end

puts Example.new Bytes.new 0

which leads to kinda expectable from verbatim usage Error: instance variable '@i' of Example was not initialized in this 'initialize', rendering it nilable. Certainly there is something I missing here, maybe someone can give a clue

Looks like it is quite simple, just need to use correct conditions:

module Encodable
  macro included
    def initialize(bytes : Bytes)
      {% verbatim do %}
        {% begin %}
          {% for v in @type.instance_vars %}
            {% if v.type == UInt32 %}
                @{{v.name.id}} = 0_u32
            {% end %}
            {% if v.type == Slice(UInt8) %}
                @{{v.name.id}} = Bytes.new 0
            {% end %}
          {% end %}
        {% end %}
      {% end %}
    end
  end
end

struct Example
  include Encodable

  getter i : UInt32
  getter b : Bytes

  def initialize(@i, @b)
  end
end

puts Example.new Bytes.new 0

I’m not really sure I follow what the use case here is. The initialize method you define doesn’t even use bytes parameter? Can you not just give default values to the ivars and remove the need for this extra module entirely?

There was just for test of fields assignment, of course there will be used something like IO::ByteFormat::LittleEndian.decode