Skip to content

Minimal working example for serializing/deserializing abstract types without exposing :type field #315

@vincent-picaud

Description

@vincent-picaud

Hi,

After some experimentation, I finally found a minimal working solution to serialize & deserialize structs containing abstract type instances.
I did not want to be intrusive by adding an extra :type field to the structures being serialized. I had a bit of trouble figuring out how to make it work, so I’m sharing my code here in case it helps someone else. Also, I’d appreciate if someone could double-check it for correctness or offer improvements.

Here's the working example:

  # Stuff to serialize/deserialize
  #
  abstract type AbstractShape end

  struct Circle <: AbstractShape
      radius::Float64                                # note: no extra :type field
  end

  struct Square <: AbstractShape
      side::Float64                                  # note: no extra :type field
  end

  struct ShapeHolder
      name::String
      shape::AbstractShape
  end

  # ================================================================

  using JSON3, StructTypes

  # ================================================================

  StructTypes.StructType(::Type{AbstractShape}) = StructTypes.AbstractType()
  StructTypes.subtypekey(::Type{AbstractShape}) = :type

  StructTypes.subtypes(::Type{AbstractShape}) = (circle=Circle,square=Square)

  # ================================================================

  StructTypes.StructType(t::Type{Circle}) = StructTypes.CustomStruct() # Mandatory otherwise lower() is not called
  StructTypes.lower(h::Circle) = Dict(
      "type" =>  "circle", 
      "radius" => h.radius
  )

  StructTypes.StructType(t::Type{Square}) = StructTypes.CustomStruct() # Mandatory otherwise lower() is not called
  StructTypes.lower(h::Square) = Dict(
      "type" =>  "square", 
      "side" => h.side
  )

  StructTypes.construct(::Type{Circle}, x::Dict) = Circle(x["radius"])
  StructTypes.construct(::Type{Square}, x::Dict) = Square(x["side"])

  # ================================================================

  function StructTypes.construct(t::Type{AbstractShape}, x::Dict)
      # field containing instance type information
      type_field = String(StructTypes.subtypekey(t))
      # get type information as string
      type_field_value = x[type_field]
      # get type
      type = StructTypes.subtypes(type_field_value)
      # create instance
      StructTypes.construct(type,x)
  end 

Now the example in action

  h = ShapeHolder("my shape", Circle(3.0))
  
  # serialize
  json_str = JSON3.write(h)         # is "{\"name\":\"my shape\",\"shape\":{\"radius\":3.0,\"type\":\"circle\"}}"

  # deserialize
 parsed = JSON3.read(json_str, ShapeHolder)     # is ShapeHolder("my shape", Circle(3.0))  

Thank you for your package !
Best,
Vincent

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions