Skip to content

Commit bac37da

Browse files
authored
Merge pull request #7 from pkgodara/fix-decode-num-keys
Fix pointer lookups for numeric keys
2 parents 6857815 + f1e2dd1 commit bac37da

4 files changed

Lines changed: 43 additions & 19 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ jobs:
3737
strategy:
3838
matrix:
3939
include:
40+
- otp: "28"
41+
elixir: "1.18"
4042
- otp: "27"
4143
elixir: "1.18"
4244
- otp: "26"

native/torque_nif/src/decoder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ fn pointer_lookup<'v>(value: &'v sonic_rs::Value, path: &str) -> Option<&'v soni
3535
let mut current = value;
3636
for segment in path[1..].split('/') {
3737
let seg_bytes = segment.as_bytes();
38-
if !seg_bytes.is_empty() && seg_bytes[0].is_ascii_digit() {
38+
if current.is_array() && !seg_bytes.is_empty() && seg_bytes[0].is_ascii_digit() {
3939
if let Ok(index) = segment.parse::<usize>() {
4040
current = current.get(index)?;
4141
continue;

test/property_test.exs

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,9 @@ defmodule Torque.PropertyTest do
66

77
# ---- Generators ----
88

9-
# Non-empty alphanumeric key, guaranteed to not be purely numeric.
10-
# Purely numeric keys (e.g. "2") are mis-routed through array-index lookup in
11-
# pointer_lookup, so they can't be reached via JSON Pointer on objects.
9+
# Non-empty alphanumeric key.
1210
defp json_key do
13-
filter(string(:alphanumeric, min_length: 1, max_length: 15), fn s ->
14-
case Integer.parse(s) do
15-
{_, ""} -> false
16-
_ -> true
17-
end
18-
end)
11+
string(:alphanumeric, min_length: 1, max_length: 15)
1912
end
2013

2114
# Scalars that roundtrip exactly through Jason → Torque (no float precision issues)
@@ -286,6 +279,17 @@ defmodule Torque.PropertyTest do
286279
assert {:error, :no_such_field} == Torque.get(doc, "/#{missing_key}")
287280
end
288281
end
282+
283+
property "get works for numeric top-level object keys" do
284+
check all(
285+
k <- integer(0..99_999_999) |> map(&Integer.to_string/1),
286+
v <- json_scalar_exact()
287+
) do
288+
json = Jason.encode!(%{k => v})
289+
{:ok, doc} = Torque.parse(json)
290+
assert {:ok, v} == Torque.get(doc, "/#{k}")
291+
end
292+
end
289293
end
290294

291295
describe "get_many consistency" do
@@ -609,15 +613,6 @@ defmodule Torque.PropertyTest do
609613
{:ok, doc2} = Torque.parse(~s({"a":[1,2,3]}))
610614
assert {:error, :no_such_field} = Torque.get(doc2, "/a/0/x")
611615
end
612-
613-
property "numeric string keys on objects always return no_such_field" do
614-
check all(n <- integer(0..100)) do
615-
json = ~s({"#{n}": "val"})
616-
{:ok, doc} = Torque.parse(json)
617-
# A purely numeric segment on an object (not an array) never matches
618-
assert {:error, :no_such_field} = Torque.get(doc, "/#{n}")
619-
end
620-
end
621616
end
622617

623618
# ---- get/3 raises on nesting_too_deep ----

test/torque_test.exs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,28 @@ defmodule Torque.PointerTest do
132132
assert geo["country"] == "US"
133133
assert geo["zip"] == "10001"
134134
end
135+
136+
test "numeric string object key is reachable via JSON Pointer" do
137+
{:ok, doc} = Torque.parse(~s({"2":"two","10":"ten"}))
138+
assert {:ok, "two"} = Torque.get(doc, "/2")
139+
assert {:ok, "ten"} = Torque.get(doc, "/10")
140+
end
141+
142+
test "nested numeric string object key is reachable via JSON Pointer" do
143+
{:ok, doc} = Torque.parse(~s({"k1":"v1","k2":{"10":"ten","n1":"nv1"}}))
144+
assert {:ok, "v1"} = Torque.get(doc, "/k1")
145+
assert {:ok, %{"10" => "ten", "n1" => "nv1"}} = Torque.get(doc, "/k2")
146+
assert {:ok, "ten"} = Torque.get(doc, "/k2/10")
147+
assert {:ok, "nv1"} = Torque.get(doc, "/k2/n1")
148+
end
149+
150+
test "numeric segment dispatches on node type" do
151+
{:ok, obj_doc} = Torque.parse(~s({"0":"from-object"}))
152+
{:ok, arr_doc} = Torque.parse(~s(["from-array"]))
153+
154+
assert {:ok, "from-object"} = Torque.get(obj_doc, "/0")
155+
assert {:ok, "from-array"} = Torque.get(arr_doc, "/0")
156+
end
135157
end
136158

137159
describe "get_many/2" do
@@ -150,6 +172,11 @@ defmodule Torque.PointerTest do
150172
assert [] = Torque.get_many(doc, [])
151173
end
152174

175+
test "numeric string object keys" do
176+
{:ok, doc} = Torque.parse(~s({"1":"one","2":"two"}))
177+
assert [{:ok, "one"}, {:ok, "two"}] = Torque.get_many(doc, ["/1", "/2"])
178+
end
179+
153180
test "all fields", %{doc: doc} do
154181
paths = [
155182
"/id",

0 commit comments

Comments
 (0)