Skip to content

Commit

Permalink
edit json encoding protocol of schemas to allow default=nil if explic…
Browse files Browse the repository at this point in the history
…itly requested
  • Loading branch information
oxg558 committed Feb 7, 2025
1 parent ec801da commit c80b083
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 18 deletions.
2 changes: 1 addition & 1 deletion lib/avrogen/avro/types.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ alias Avrogen.Avro.Types
import Avrogen.Utils.MacroUtils

jason_decode_skip_null_impl(Types.Record)
jason_decode_skip_null_impl(Types.Record.Field)
jason_decode_skip_null_field_impl(Types.Record.Field)
jason_decode_skip_null_impl(Types.Enum)
27 changes: 15 additions & 12 deletions lib/avrogen/avro/types/record/field.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ defmodule Avrogen.Avro.Types.Record.Field do

@type order :: :ascending | :descending | :ignore

@explicitly_null_default "explicitly_null_default"

typedstruct do
# A JSON string providing the name of the enum (required).
field :name, String.t(), enforce: true
Expand Down Expand Up @@ -40,11 +42,20 @@ defmodule Avrogen.Avro.Types.Record.Field do
end

def parse(%{"name" => name, "type" => type} = field) do
# We must be able to differentiate between default=nil and default not set.
default =
case Map.fetch(field, "default") do
:error -> nil
{:ok, "null"} -> @explicitly_null_default
{:ok, nil} -> @explicitly_null_default
{:ok, other} -> other
end

%__MODULE__{
name: name,
doc: field["doc"],
type: Schema.parse(type),
default: parse_default_value(field["default"]),
default: default,
order: field["order"],
aliases: field["aliases"],
pii: field["pii"] || false,
Expand All @@ -57,11 +68,6 @@ defmodule Avrogen.Avro.Types.Record.Field do
def comment(%__MODULE__{doc: nil, name: name}), do: "#{name}: #{name}"
def comment(%__MODULE__{doc: doc, name: name}), do: "#{name}: #{doc}"

# The only way to differentiate between "default is nil because there isn't one" and "default is actually null" is to
# check if the type is also a union with null.
def has_default?(%__MODULE__{default: nil, type: %Types.Union{} = union}),
do: Types.Union.optional_by_convention?(union)

def has_default?(%__MODULE__{default: nil}), do: false
def has_default?(%__MODULE__{}), do: true

Expand All @@ -82,15 +88,16 @@ defmodule Avrogen.Avro.Types.Record.Field do
variable_name = Code.string_to_quoted!(name)

case default do
default when is_nil(default) or default == "null" ->
default when default == @explicitly_null_default ->
quote(do: unquote(variable_name) = avro_map[unquote(name)])

default ->
quote(do: unquote(variable_name) = avro_map[unquote(name)] || unquote(default))
end
end

def typed_struct_field(%__MODULE__{name: name, type: type, default: nil} = field) do
def typed_struct_field(%__MODULE__{name: name, type: type, default: default} = field)
when default == @explicitly_null_default do
name = String.to_atom(name)

quote do
Expand All @@ -109,10 +116,6 @@ defmodule Avrogen.Avro.Types.Record.Field do
end
end

def parse_default_value(nil), do: nil
def parse_default_value("null"), do: nil
def parse_default_value(value), do: value

def enforce?(%__MODULE__{type: %Types.Union{} = union}),
do: not Types.Union.has_member?(union, Types.Primitive.null())

Expand Down
5 changes: 0 additions & 5 deletions lib/avrogen/avro/types/union.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ defmodule Avrogen.Avro.Types.Union do
"""

alias Avrogen.Avro.Schema
alias Avrogen.Avro.Types.Primitive
use TypedStruct

typedstruct do
Expand All @@ -26,10 +25,6 @@ defmodule Avrogen.Avro.Types.Union do
def parse(value) when is_list(value), do: %__MODULE__{types: Enum.map(value, &Schema.parse/1)}

def has_member?(%__MODULE__{types: types}, type), do: Enum.member?(types, type)

# A union with null listed first is optional by convention (see moduledoc).
def optional_by_convention?(%__MODULE__{types: [first_type | _]}),
do: first_type == Primitive.null()
end

alias Avrogen.Avro.Schema.CodeGenerator
Expand Down
23 changes: 23 additions & 0 deletions lib/avrogen/util/macro.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,27 @@ defmodule Avrogen.Utils.MacroUtils do
end
end
end

defmacro jason_decode_skip_null_field_impl(type) do
quote do
defimpl Jason.Encoder, for: unquote(type) do
def encode(%unquote(type){} = value, opts) do
value
|> Map.from_struct()
|> Map.reject(fn {_k, v} -> is_nil(v) end)
|> then(fn map ->
# TODO get from config?
case Map.get(map, :default) do
"explicitly_null_default" ->
Map.put(map, :default, nil)

_other ->
map
end
end)
|> Jason.Encode.map(opts)
end
end
end
end
end

0 comments on commit c80b083

Please sign in to comment.