Use OTP json encoder by default. Add support for custom json library. (#199)

* add support for custom json library besides jason

* use native erlang json encoder

* fixed json parsing.  added more json tests

* Prepare for 0.17.0 release
This commit is contained in:
Denis Donici 2026-01-22 11:51:00 +02:00 committed by GitHub
parent d21239ec3e
commit 1f9ebc49d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 567 additions and 43 deletions

View file

@ -5,7 +5,22 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## UNRELEASED
## 0.17.0 - 2026-01-22
### Breaking Changes
- **Minimum OTP version is now 27** - LiveSvelte now uses Erlang's native `:json` module by default
- **Minimum Elixir version is now 1.17** - Required for OTP 27 support
- **Jason is now optional** - Add `{:jason, "~> 1.2"}` to your deps if you want to use Jason instead of native JSON
### Changed
- Default JSON library changed from Jason to native Erlang `:json` module (`LiveSvelte.JSON`)
- Structs are automatically converted to maps by the native JSON encoder (no `@derive` needed)
### Added
- New `LiveSvelte.JSON` module that wraps Erlang's native `:json` module with a Jason-compatible interface
## 0.16.0 - 2025-04-18

View file

@ -92,7 +92,7 @@ _If you're updating from an older version, make sure to check the `CHANGELOG.md`
```elixir
defp deps do
[
{:live_svelte, "~> 0.16.0"}
{:live_svelte, "~> 0.17.0"}
]
end
```
@ -487,6 +487,43 @@ To disable SSR on a specific component, set the `ssr` property to false. Like so
<.svelte name="Example" ssr={false} />
```
### JSON Library
LiveSvelte uses Erlang/OTP 27's native `:json` module by default for JSON encoding.
This provides excellent performance without requiring external dependencies.
**Note:** LiveSvelte requires Elixir 1.17+ and OTP 27+ for the native JSON module.
#### Using Jason or Poison
If you prefer to use Jason, Poison, or another JSON library, configure it in your `config.exs`:
```elixir
# config/config.exs
config :live_svelte, json_library: Jason
```
Add the dependency to your `mix.exs`:
```elixir
# mix.exs
defp deps do
[
{:live_svelte, "~> 0.17"},
{:jason, "~> 1.2"} # or {:poison, "~> 5.0"}
]
end
```
The JSON library must implement `encode!/1` that accepts any Elixir term and returns a JSON string.
#### Struct Encoding
The native JSON encoder automatically converts structs to maps before encoding. This means
you don't need `@derive Jason.Encoder` when using the default native JSON encoder.
If you're using Jason and need custom struct encoding behavior, see the
[Structs and Ecto](#structs-and-ecto) section for details on `@derive Jason.Encoder`.
### live_json
@ -527,12 +564,15 @@ You can find an example [here](https://github.com/woutdp/live_svelte/blob/master
### Structs and Ecto
We use [Jason](https://github.com/michalmuskala/jason) to serialize any data you pass in the props so it can be handled by Javascript.
Jason doesn't know how to handle structs by default, so you need to define it yourself.
LiveSvelte serializes data passed in props to JSON so it can be handled by JavaScript.
#### Structs
**With native JSON (default):** Structs are automatically converted to maps. No additional configuration needed.
For example, if you have a regular struct like this:
**With Jason:** Jason doesn't know how to handle structs by default, so you need to define it yourself using `@derive`.
#### Structs (Jason only)
If you're using Jason and have a struct like this:
```elixir
defmodule User do
@ -558,9 +598,9 @@ defmodule User do
end
```
#### Ecto
#### Ecto (Jason only)
In ecto's case it's important to _also_ omit the `__meta__` field as it's not serializable.
When using Jason with Ecto schemas, it's important to _also_ omit the `__meta__` field as it's not serializable.
Check out the following example:

View file

@ -31,7 +31,6 @@
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
@ -454,7 +453,6 @@
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
@ -480,7 +478,6 @@
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.0.0"
}
@ -508,8 +505,7 @@
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/acorn": {
"version": "8.14.0",
@ -531,7 +527,6 @@
"integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==",
"dev": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"acorn": ">=8.9.0"
}
@ -542,7 +537,6 @@
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">= 0.4"
}
@ -553,7 +547,6 @@
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">= 0.4"
}
@ -565,6 +558,7 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"bin": {
"esbuild": "bin/esbuild"
},
@ -620,8 +614,7 @@
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.1.4.tgz",
"integrity": "sha512-oO82nKPHKkzIj/hbtuDYy/JHqBHFlMIW36SDiPCVsj87ntDLcWN+sJ1erdVryd4NxODacFTsdrIE3b7IamqbOg==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/esrap": {
"version": "1.2.2",
@ -629,7 +622,6 @@
"integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15",
"@types/estree": "^1.0.1"
@ -641,7 +633,6 @@
"integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/estree": "*"
}
@ -651,8 +642,7 @@
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/magic-string": {
"version": "0.30.12",
@ -660,7 +650,6 @@
"integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0"
}
@ -708,8 +697,7 @@
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz",
"integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
}
}
}

View file

@ -3,6 +3,7 @@ import Config
config :live_svelte,
ssr_module: LiveSvelte.SSR.NodeJS,
ssr: true
# json_library defaults to LiveSvelte.JSON (native Erlang :json module)
# if Mix.env() == :dev do
# esbuild = fn args ->

View file

@ -94,19 +94,20 @@ defmodule LiveSvelte do
<script>
<%= raw(@ssr_render["head"]) %>
</script>
<div
id={id(@name)}
data-name={@name}
data-props={json(@props)}
data-ssr={@ssr_render != nil}
data-live-json={
if @init, do: json(@live_json_props), else: @live_json_props |> Map.keys() |> json()
}
data-slots={@slots |> Slots.base_encode_64() |> json}
phx-update="ignore"
phx-hook="SvelteHook"
class={@class}
>
<% svelte_id = id(@name) %>
<div
id={svelte_id}
data-name={@name}
data-props={json(@props)}
data-ssr={@ssr_render != nil}
data-live-json={
if @init, do: json(@live_json_props), else: @live_json_props |> Map.keys() |> json()
}
data-slots={@slots |> Slots.base_encode_64() |> json}
phx-hook="SvelteHook"
class={@class}
>
<div id={"#{svelte_id}-target"} data-svelte-target phx-update="ignore">
<%= raw(@ssr_render["head"]) %>
<style>
<%= raw(@ssr_render["css"]["code"]) %>
@ -114,6 +115,7 @@ defmodule LiveSvelte do
<%= raw(@ssr_render["html"]) %>
<%= render_slot(@loading) %>
</div>
</div>
</.live_json>
"""
end
@ -128,7 +130,8 @@ defmodule LiveSvelte do
end
defp json(props) do
Jason.encode!(props)
json_library = Application.get_env(:live_svelte, :json_library, LiveSvelte.JSON)
json_library.encode!(props)
end
defp id(name), do: "#{name}-#{System.unique_integer([:positive])}"

102
lib/live_svelte/json.ex Normal file
View file

@ -0,0 +1,102 @@
defmodule LiveSvelte.JSON do
@moduledoc """
JSON encoding using Erlang/OTP 27's native :json module.
This module provides a Jason-compatible interface (`encode!/1`)
that wraps the native Erlang :json module for use with LiveSvelte.
## Features
- Uses Erlang's built-in `:json` module (OTP 27+)
- Automatically converts structs to maps
- Converts all map keys to strings (matching Jason behavior)
- Handles nested data structures
## Usage
This module is the default JSON encoder for LiveSvelte. To use a different
encoder like Jason, configure it in your `config.exs`:
config :live_svelte, json_library: Jason
## SSR Compatibility Note
When using server-side rendering, the NodeJS worker uses Jason internally
to serialize data to the Node.js process. This module is designed to produce
Jason-compatible output, ensuring consistency between SSR and client-side
hydration.
"""
@doc """
Encodes an Elixir term to a JSON string.
Structs are automatically converted to maps before encoding.
Returns a binary string.
## Examples
iex> LiveSvelte.JSON.encode!(%{foo: "bar"})
"{\"foo\":\"bar\"}"
iex> LiveSvelte.JSON.encode!([1, 2, 3])
"[1,2,3]"
"""
@spec encode!(term()) :: binary()
def encode!(term) do
term
|> prepare_term()
|> :json.encode()
|> IO.iodata_to_binary()
end
# Recursively prepare terms for JSON encoding.
# Converts structs to maps, nil to null, and handles nested structures.
# nil becomes JSON null
defp prepare_term(nil), do: :null
# Booleans pass through (Erlang :json handles them)
defp prepare_term(true), do: true
defp prepare_term(false), do: false
# Other atoms become strings (matches Jason behavior)
defp prepare_term(atom) when is_atom(atom) do
Atom.to_string(atom)
end
# Structs become maps (strip __struct__ key)
defp prepare_term(%_{} = struct) do
struct
|> Map.from_struct()
|> prepare_term()
end
# Maps: convert all keys to strings, recursively process values
defp prepare_term(map) when is_map(map) do
Map.new(map, fn {k, v} -> {prepare_key(k), prepare_term(v)} end)
end
# Lists: recursively process elements
defp prepare_term(list) when is_list(list) do
Enum.map(list, &prepare_term/1)
end
# Tuples become arrays
defp prepare_term(tuple) when is_tuple(tuple) do
tuple
|> Tuple.to_list()
|> prepare_term()
end
# Numbers and binaries pass through
defp prepare_term(term), do: term
# Key conversion helpers - ensure all keys become strings
defp prepare_key(key) when is_atom(key), do: Atom.to_string(key)
defp prepare_key(key) when is_integer(key), do: Integer.to_string(key)
defp prepare_key(key) when is_float(key), do: Float.to_string(key)
defp prepare_key(key) when is_binary(key), do: key
defp prepare_key(key), do: to_string(key)
end

10
mix.exs
View file

@ -1,14 +1,15 @@
defmodule LiveSvelte.MixProject do
use Mix.Project
@version "0.16.0"
@version "0.17.0"
@repo_url "https://github.com/woutdp/live_svelte"
def project do
[
app: :live_svelte,
version: @version,
elixir: "~> 1.12",
elixir: "~> 1.17",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
aliases: aliases(),
deps: deps(),
@ -53,10 +54,13 @@ defmodule LiveSvelte.MixProject do
]
end
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
defp deps do
[
{:ex_doc, "~> 0.37.3", only: :dev, runtime: false},
{:jason, "~> 1.2"},
{:jason, "~> 1.2", optional: true},
{:nodejs, "~> 3.1"},
{:phoenix, ">= 1.7.0"},
{:phoenix_html, ">= 3.3.1"},

View file

@ -1,6 +1,6 @@
{
"name": "live_svelte",
"version": "0.16.0",
"version": "0.17.0",
"description": "",
"license": "MIT",
"module": "./priv/static/live_svelte.esm.js",

152
test/json_library_test.exs Normal file
View file

@ -0,0 +1,152 @@
defmodule LiveSvelte.JSONLibraryTest do
# must be synchronous, tests are sensitive to config changes
use ExUnit.Case, async: false
describe "native JSON library (default)" do
test "uses LiveSvelte.JSON by default when no config is provided" do
Application.delete_env(:live_svelte, :json_library)
json_library = Application.get_env(:live_svelte, :json_library, LiveSvelte.JSON)
assert json_library == LiveSvelte.JSON
end
test "encodes props correctly with native JSON" do
Application.delete_env(:live_svelte, :json_library)
data = %{foo: "bar", baz: 123}
# Call the private json/1 function through the public API
# by testing that the component renders with correct data attributes
assigns = %{
__changed__: nil,
socket: nil,
name: "Test",
props: data,
live_json_props: %{},
ssr: false,
class: nil,
loading: [],
inner_block: []
}
result = LiveSvelte.svelte(assigns)
html = Phoenix.HTML.Safe.to_iodata(result) |> IO.iodata_to_binary()
# HTML entities are escaped in the output
# Native JSON encodes correctly
assert html =~ "data-props="
assert html =~ "foo"
assert html =~ "bar"
assert html =~ "baz"
assert html =~ "123"
end
end
describe "custom JSON library" do
test "uses custom library when configured" do
load_config("test/json_library_test/custom_json_config.exs")
json_library = Application.get_env(:live_svelte, :json_library)
assert json_library == TestJSONLibrary
end
test "calls custom library's encode!/1 function" do
load_config("test/json_library_test/custom_json_config.exs")
data = %{test: "data"}
assigns = %{
__changed__: nil,
socket: nil,
name: "Test",
props: data,
live_json_props: %{},
ssr: false,
class: nil,
loading: [],
inner_block: []
}
result = LiveSvelte.svelte(assigns)
html = Phoenix.HTML.Safe.to_iodata(result) |> IO.iodata_to_binary()
# Should contain the test library's output
assert html =~ ~s(data-props="TEST_ENCODED:%{test: &quot;data&quot;}")
end
end
describe "backward compatibility with Jason" do
test "works with Jason when explicitly configured" do
Application.put_env(:live_svelte, :json_library, Jason)
json_library = Application.get_env(:live_svelte, :json_library)
assert json_library == Jason
data = %{legacy: "test"}
assigns = %{
__changed__: nil,
socket: nil,
name: "Legacy",
props: data,
live_json_props: %{},
ssr: false,
class: nil,
loading: [],
inner_block: []
}
result = LiveSvelte.svelte(assigns)
html = Phoenix.HTML.Safe.to_iodata(result) |> IO.iodata_to_binary()
# HTML entities are escaped in the output
assert html =~ ~s(data-props="{&quot;legacy&quot;:&quot;test&quot;}")
# Clean up
Application.delete_env(:live_svelte, :json_library)
end
end
describe "struct encoding" do
defmodule TestUser do
defstruct name: "John", age: 27
end
test "native JSON encodes structs as maps automatically" do
Application.delete_env(:live_svelte, :json_library)
data = %{user: %TestUser{name: "Jane", age: 30}}
assigns = %{
__changed__: nil,
socket: nil,
name: "StructTest",
props: data,
live_json_props: %{},
ssr: false,
class: nil,
loading: [],
inner_block: []
}
result = LiveSvelte.svelte(assigns)
html = Phoenix.HTML.Safe.to_iodata(result) |> IO.iodata_to_binary()
# Should encode struct fields
assert html =~ "user"
assert html =~ "name"
assert html =~ "Jane"
assert html =~ "age"
assert html =~ "30"
end
end
# Helper function to reload configuration
# Pattern copied from ssr_test.exs:27-34
defp load_config(path) do
Application.started_applications(:infinity)
|> Enum.reduce([], fn {app, _, _}, acc ->
[{app, Application.get_all_env(app)} | acc]
end)
|> Config.Reader.merge(Config.Reader.read!(path))
|> Application.put_all_env()
end
end

View file

@ -0,0 +1,3 @@
import Config
config :live_svelte, json_library: TestJSONLibrary

View file

@ -0,0 +1,208 @@
defmodule LiveSvelte.JSONTest do
use ExUnit.Case, async: true
alias LiveSvelte.JSON
describe "encode!/1" do
test "encodes simple maps" do
assert JSON.encode!(%{foo: "bar"}) == ~s({"foo":"bar"})
end
test "encodes nested maps" do
result = JSON.encode!(%{outer: %{inner: "value"}})
assert result == ~s({"outer":{"inner":"value"}})
end
test "encodes lists" do
assert JSON.encode!([1, 2, 3]) == "[1,2,3]"
end
test "encodes lists of maps" do
result = JSON.encode!([%{a: 1}, %{b: 2}])
assert result == ~s([{"a":1},{"b":2}])
end
test "encodes integers" do
assert JSON.encode!(42) == "42"
end
test "encodes floats" do
assert JSON.encode!(3.14) == "3.14"
end
test "encodes booleans" do
assert JSON.encode!(true) == "true"
assert JSON.encode!(false) == "false"
end
test "encodes nil as null" do
assert JSON.encode!(nil) == "null"
end
test "encodes strings" do
assert JSON.encode!("hello") == ~s("hello")
end
test "encodes atoms as strings" do
assert JSON.encode!(:hello) == ~s("hello")
end
test "encodes maps with atom keys" do
result = JSON.encode!(%{foo: "bar", baz: 123})
# Map key order may vary, so we parse and compare
assert result =~ "foo"
assert result =~ "bar"
assert result =~ "baz"
assert result =~ "123"
end
test "encodes maps with string keys" do
assert JSON.encode!(%{"foo" => "bar"}) == ~s({"foo":"bar"})
end
end
describe "struct encoding" do
defmodule TestStruct do
defstruct name: "test", value: 42
end
defmodule NestedStruct do
defstruct user: nil, data: %{}
end
test "encodes structs as maps" do
result = JSON.encode!(%TestStruct{})
decoded = :json.decode(result)
assert decoded["name"] == "test"
assert decoded["value"] == 42
end
test "encodes structs with custom values" do
result = JSON.encode!(%TestStruct{name: "custom", value: 100})
decoded = :json.decode(result)
assert decoded["name"] == "custom"
assert decoded["value"] == 100
end
test "encodes nested structs" do
nested = %NestedStruct{
user: %TestStruct{name: "john", value: 30},
data: %{key: "value"}
}
result = JSON.encode!(nested)
decoded = :json.decode(result)
assert decoded["user"]["name"] == "john"
assert decoded["user"]["value"] == 30
assert decoded["data"]["key"] == "value"
end
test "encodes structs in lists" do
list = [%TestStruct{name: "a"}, %TestStruct{name: "b"}]
result = JSON.encode!(list)
decoded = :json.decode(result)
assert length(decoded) == 2
assert Enum.at(decoded, 0)["name"] == "a"
assert Enum.at(decoded, 1)["name"] == "b"
end
end
describe "tuple encoding" do
test "encodes tuples as arrays" do
assert JSON.encode!({1, 2, 3}) == "[1,2,3]"
end
test "encodes tuples with mixed types" do
result = JSON.encode!({"hello", 42, true})
assert result == ~s(["hello",42,true])
end
end
describe "edge cases" do
test "encodes empty map" do
assert JSON.encode!(%{}) == "{}"
end
test "encodes empty list" do
assert JSON.encode!([]) == "[]"
end
test "encodes deeply nested structures" do
deep = %{a: %{b: %{c: %{d: [1, 2, %{e: "f"}]}}}}
result = JSON.encode!(deep)
decoded = :json.decode(result)
assert get_in(decoded, ["a", "b", "c", "d"]) |> Enum.at(2) |> Map.get("e") == "f"
end
end
describe "integer key handling" do
test "encodes maps with integer keys as string keys" do
result = JSON.encode!(%{1 => "a", 2 => "b"})
decoded = :json.decode(result)
assert Map.has_key?(decoded, "1")
assert Map.has_key?(decoded, "2")
assert decoded["1"] == "a"
assert decoded["2"] == "b"
end
test "encodes large maps with integer keys (LiveJson scenario)" do
data = for i <- 1..100, into: %{}, do: {i, i * 2}
result = JSON.encode!(data)
decoded = :json.decode(result)
assert decoded["1"] == 2
assert decoded["50"] == 100
assert decoded["100"] == 200
end
test "encodes nested maps with integer keys" do
data = %{1 => %{2 => "nested"}}
result = JSON.encode!(data)
decoded = :json.decode(result)
assert decoded["1"]["2"] == "nested"
end
test "encodes mixed key types" do
data = %{1 => "int", :atom => "atom", "string" => "string"}
result = JSON.encode!(data)
decoded = :json.decode(result)
assert decoded["1"] == "int"
assert decoded["atom"] == "atom"
assert decoded["string"] == "string"
end
end
describe "atom value handling" do
test "encodes atom values as strings" do
result = JSON.encode!(%{status: :active})
decoded = :json.decode(result)
assert decoded["status"] == "active"
end
test "preserves boolean atoms" do
assert JSON.encode!(true) == "true"
assert JSON.encode!(false) == "false"
end
test "encodes atom values in lists" do
result = JSON.encode!([:one, :two, :three])
assert result == ~s(["one","two","three"])
end
test "encodes mixed atom and string values" do
result = JSON.encode!(%{atom_val: :test, string_val: "test"})
decoded = :json.decode(result)
assert decoded["atom_val"] == "test"
assert decoded["string_val"] == "test"
end
end
describe "float key handling" do
test "encodes maps with float keys as string keys" do
result = JSON.encode!(%{1.5 => "float"})
decoded = :json.decode(result)
# Float.to_string may produce different representations
assert map_size(decoded) == 1
assert Enum.at(Map.values(decoded), 0) == "float"
end
end
end

View file

@ -0,0 +1,8 @@
defmodule TestJSONLibrary do
@moduledoc false
# Mock JSON library for testing custom configuration
def encode!(data) do
"TEST_ENCODED:#{inspect(data)}"
end
end