Repository: elixir-protobuf/protobuf
Pull request #393 (2025-01-04 22:49:25)

Conformance: accept nulls for repeated and maps in JSON decoder

v0idpwn: Fixes the last 2 failing required tests in the conformance suite from protobuf main. 



Patch:
diff --git a/lib/protobuf/json/decode.ex b/lib/protobuf/json/decode.ex
index bea70fa..8b573ca 100644
--- a/lib/protobuf/json/decode.ex
+++ b/lib/protobuf/json/decode.ex
@@ -278,6 +278,8 @@ defmodule Protobuf.JSON.Decode do
     end
   end
 
+  defp decode_map(_prop, nil), do: nil
+
   defp decode_map(prop, bad_map), do: throw({:bad_map, prop.name_atom, bad_map})
 
   defp decode_key(type, key, field) when is_binary(key) do
@@ -301,6 +303,8 @@ defmodule Protobuf.JSON.Decode do
     for val <- value, do: decode_singular(prop, val)
   end
 
+  defp decode_repeated(_prop, nil), do: nil
+
   defp decode_repeated(prop, value) do
     throw({:bad_repeated, prop.name_atom, value})
   end


Test patch:
diff --git a/test/protobuf/conformance_regressions_test.exs b/test/protobuf/conformance_regressions_test.exs
index dc5e992..3b29dbf 100644
--- a/test/protobuf/conformance_regressions_test.exs
+++ b/test/protobuf/conformance_regressions_test.exs
@@ -57,6 +57,33 @@ defmodule Protobuf.ConformanceRegressionsTest do
     end
   end
 
+  test "Required.Proto2.ProtobufInput.ValidDataMap.INT32.INT32.MissingDefault.JsonOutput" do
+    mod = ProtobufTestMessages.Proto2.TestAllTypesProto2
+    problematic_payload = <<194, 3, 0>>
+    assert %{map_int32_int32: %{0 => 0}} = mod.decode(problematic_payload)
+  end
+
+  test "Required.Proto3.JsonInput.Int32FieldQuotedExponentialValue.JsonOutput" do
+    mod = ProtobufTestMessages.Proto3.TestAllTypesProto3
+    problematic_payload = ~S({"optionalInt32": "1e5"})
+    assert %{optional_int32: 100_000} = Protobuf.JSON.decode!(problematic_payload, mod)
+  end
+
+  test "Required.Proto2.JsonInput.AllFieldAcceptNull.ProtobufOutput" do
+    mod = ProtobufTestMessages.Proto2.TestAllTypesProto2
+    problematic_payload = ~S({
+      "map_bool_bool": null,
+      "repeated_int32": null
+    })
+
+    assert %{
+             map_bool_bool: map_bool_bool,
+             repeated_int32: []
+           } = Protobuf.JSON.decode!(problematic_payload, mod)
+
+    assert is_map(map_bool_bool) and map_size(map_bool_bool) == 0
+  end
+
   describe "proto2" do
     setup :url_to_message
     setup :decode_conformance_input
@@ -83,18 +110,6 @@ defmodule Protobuf.ConformanceRegressionsTest do
 
     @describetag message_type: "protobuf_test_messages.proto3.TestAllTypesProto3"
 
-    test "Required.Proto2.ProtobufInput.ValidDataMap.INT32.INT32.MissingDefault.JsonOutput" do
-      mod = ProtobufTestMessages.Proto2.TestAllTypesProto2
-      problematic_payload = <<194, 3, 0>>
-      assert %{map_int32_int32: %{0 => 0}} = mod.decode(problematic_payload)
-    end
-    
-    test "Required.Proto3.JsonInput.Int32FieldQuotedExponentialValue.JsonOutput" do
-      mod = ProtobufTestMessages.Proto3.TestAllTypesProto3
-      problematic_payload = ~S({"optionalInt32": "1e5"})
-      assert %{optional_int32: 100_000} = Protobuf.JSON.decode!(problematic_payload, mod)
-    end
-
     test "Recommended.Proto3.JsonInput.NullValueInOtherOneofNewFormat.Validator",
          %{message_mod: message_mod} do
       json = "{\"oneofNullValue\": null}"