quinnj / JSON3.jl

Other
214 stars 47 forks source link

Alternative to wrapper types? #254

Closed kdheepak closed 1 year ago

kdheepak commented 1 year ago

Thanks for writing and maintaining this package!

I'm trying to write a JSON3 StructTypes interface to the JSON created by Pandoc.

For a Markdown file like this:

# Pandoc.jl

[![Build Status](https://travis-ci.com/kdheepak/Pandoc.jl.svg?branch=master)](https://travis-ci.com/kdheepak/Pandoc.jl)
[![Documentation](https://img.shields.io/badge/docs-ready-blue.svg)](https://kdheepak.github.io/Pandoc.jl/stable)

[Pandoc.jl](https://github.com/kdheepak/Pandoc.jl) is a package to make it easier to write filters for [Pandoc](https://github.com/jgm/pandoc) in Julia.

## Install

To install Pandoc.jl, open the Julia package manager prompt and type:

```julia
(v1.8) pkg> add Pandoc

Quick Start

julia> using Pandoc, FilePaths, Test

julia> doc = Pandoc.Document(p"./test/data/writer.markdown");

julia> @test doc.pandoc_api_version == v"1.23"

julia> @test length(doc.blocks) == 239

The Pandoc JSON looks like this:

```json
{
  "pandoc-api-version": [
    1,
    22,
    2,
    1
  ],
  "meta": {},
  "blocks": [
    {
      "t": "Header",
      "c": [
        1,
        [
          "pandoc.jl",
          [],
          []
        ],
        [
          {
            "t": "Str",
            "c": "Pandoc.jl"
          }
        ]
      ]
    },
    {
      "t": "Para",
      "c": [
        {
          "t": "Link",
          "c": [
            [
              "",
              [],
              []
            ],
            [
              {
                "t": "Image",
                "c": [
                  [
                    "",
                    [],
                    []
                  ],
                  [
                    {
                      "t": "Str",
                      "c": "Build"
                    },
                    {
                      "t": "Space"
                    },
                    {
                      "t": "Str",
                      "c": "Status"
                    }
                  ],
                  [
                    "https://travis-ci.com/kdheepak/Pandoc.jl.svg?branch=master",
                    ""
                  ]
                ]
              }
            ],
            [
              "https://travis-ci.com/kdheepak/Pandoc.jl",
              ""
            ]
          ]
        },
        {
          "t": "SoftBreak"
        },
        {
          "t": "Link",
          "c": [
            [
              "",
              [],
              []
            ],
            [
              {
                "t": "Image",
                "c": [
                  [
                    "",
                    [],
                    []
                  ],
                  [
                    {
                      "t": "Str",
                      "c": "Documentation"
                    }
                  ],
                  [
                    "https://img.shields.io/badge/docs-ready-blue.svg",
                    ""
                  ]
                ]
              }
            ],
            [
              "https://kdheepak.github.io/Pandoc.jl/stable",
              ""
            ]
          ]
        }
      ]
    },
    {
      "t": "Para",
      "c": [
        {
          "t": "Link",
          "c": [
            [
              "",
              [],
              []
            ],
            [
              {
                "t": "Str",
                "c": "Pandoc.jl"
              }
            ],
            [
              "https://github.com/kdheepak/Pandoc.jl",
              ""
            ]
          ]
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "is"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "a"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "package"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "to"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "make"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "it"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "easier"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "to"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "write"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "filters"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "for"
        },
        {
          "t": "Space"
        },
        {
          "t": "Link",
          "c": [
            [
              "",
              [],
              []
            ],
            [
              {
                "t": "Str",
                "c": "Pandoc"
              }
            ],
            [
              "https://github.com/jgm/pandoc",
              ""
            ]
          ]
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "in"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "Julia."
        }
      ]
    },
    {
      "t": "Header",
      "c": [
        2,
        [
          "install",
          [],
          []
        ],
        [
          {
            "t": "Str",
            "c": "Install"
          }
        ]
      ]
    },
    {
      "t": "Para",
      "c": [
        {
          "t": "Str",
          "c": "To"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "install"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "Pandoc.jl,"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "open"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "the"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "Julia"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "package"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "manager"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "prompt"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "and"
        },
        {
          "t": "Space"
        },
        {
          "t": "Str",
          "c": "type:"
        }
      ]
    },
    {
      "t": "CodeBlock",
      "c": [
        [
          "",
          [
            "julia"
          ],
          []
        ],
        "(v1.8) pkg> add Pandoc"
      ]
    },
    {
      "t": "Header",
      "c": [
        2,
        [
          "quick-start",
          [],
          []
        ],
        [
          {
            "t": "Str",
            "c": "Quick"
          },
          {
            "t": "Space"
          },
          {
            "t": "Str",
            "c": "Start"
          }
        ]
      ]
    },
    {
      "t": "CodeBlock",
      "c": [
        [
          "",
          [
            "julia"
          ],
          []
        ],
        "julia> using Pandoc, FilePaths, Test\n\njulia> doc = Pandoc.Document(p\"./test/data/writer.markdown\");\n\njulia> @test doc.pandoc_api_version == v\"1.23\"\n\njulia> @test length(doc.blocks) == 239"
      ]
    }
  ]
}

There's 15 Block types:

Plain 
Para 
LineBlock 
CodeBlock 
RawBlock 
BlockQuote 
OrderedList 
BulletList 
DefinitionList 
Header 
HorizontalRule 
Table 
Figure 
Div 
Null 

And 20 Inline types:

Str 
Emph 
Underline 
Strong 
Strikeout 
Superscript 
Subscript 
SmallCaps 
Quoted 
Cite 
Code 
Space 
SoftBreak 
LineBreak 
Math 
RawInline 
Link 
Image 
Note 
Span 

In the JSON, every type is represented by a tag under the t key, and the content under a c key.

For example, this represents the inline Space.

          {
            "t": "Space"
          },

Or this represents a Link:

       {
          "t": "Link",
          "c": [
            [
              "",
              [],
              []
            ],
            [
              {
                "t": "Str",
                "c": "Pandoc.jl"
              }
            ],
            [
              "https://github.com/kdheepak/Pandoc.jl",
              ""
            ]
          ]
        },

In Julia, I'm representing this Space type like so:

"""
Inter-word space
"""
struct Space <: Inline end

and the Link type like so:

"""
Link target (URL, title).
"""
Base.@kwdef struct Target
  url::Text = ""
  title::Text = ""
end

Base.@kwdef struct Link <: Inline
  attr::Attr = Attr()
  content::Vector{Inline} = []
  target::Target = Target()
end

The example in the document suggests that I have to do something like this:

struct Person
    id::Int
    name::String
end
StructTypes.StructType(::Type{Person}) = StructTypes.Struct()
struct PersonWrapper
    person::Person
end
StructTypes.StructType(::Type{PersonWrapper}) = StructTypes.CustomStruct()
StructTypes.lower(x::PersonWrapper) = x.person
StructTypes.lowertype(::Type{PersonWrapper}) = Person
StructTypes.construct(::Type{PersonWrapper}, x::Person) = PersonWrapper(x) 

To have something like JSON3.read(pandoc_json, Document), it appears that I need to write Wrapper types for every one of the Julia types I have mentioned above (35 structs), which seems like a lot of code to write and maintain just for a wrapper type.

Is there an alternative to using wrapper types? If not, should there be a macro of some kind to support this functionality for the package.

For comparison, in Rust you can use serde(tag = "t", content = "c") as a procedural macro that automatically solve this kind of problem for you.

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(tag = "t", content = "c")]
pub enum MetaValue {
    MetaMap(HashMap<String, MetaValue>),
    MetaList(Vec<MetaValue>),
    MetaBool(bool),
    MetaString(String),
    MetaInlines(Vec<Inline>),
    MetaBlocks(Vec<Block>),
}

Also, (maybe somewhat unrelated), in Julia I'm using Enums. Is that possible to serialize and deserialize from json too?

kdheepak commented 1 year ago

It appears that I can implement the CustomStruct trait directly on the structs that I have, I misunderstood the documentation. I'll close this issue!