This is an automated email from the ASF dual-hosted git repository.

CurtHagenlocher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-dotnet.git


The following commit(s) were added to refs/heads/main by this push:
     new a25e49b  Refactors Apache.Arrow.Variant into two assemblies (#322)
a25e49b is described below

commit a25e49b411c614ec1203e8888a3df4e66a4d7c31
Author: Curt Hagenlocher <[email protected]>
AuthorDate: Mon Apr 20 20:22:41 2026 -0700

    Refactors Apache.Arrow.Variant into two assemblies (#322)
    
    ## What's Changed
    
    Refactors `Apache.Arrow.Variant` into two assemblies -- one for the base
    type and one for JSON conversion. The dependency on `System.Text.Json`
    moves to the second assembly.
---
 Apache.Arrow.Tests.slnf                            |   3 +-
 Apache.Arrow.sln                                   | 160 +++++
 dev/release/verify_rc.sh                           |   1 +
 .../Apache.Arrow.Operations.csproj}                |   8 +-
 .../Json/VariantJsonConverter.cs                   |   4 +-
 .../Json/VariantJsonReader.cs                      | 152 +++++
 .../Json/VariantJsonWriter.cs                      |   4 +-
 .../Properties/AssemblyInfo.cs                     |   2 +-
 .../Apache.Arrow.Variant.csproj                    |   1 -
 src/Apache.Arrow.Variant/Json/VariantJsonReader.cs |  49 --
 .../Properties/AssemblyInfo.cs                     |   1 +
 src/Apache.Arrow.Variant/VariantBuilder.cs         | 720 ++-------------------
 src/Apache.Arrow.Variant/VariantValueWriter.cs     | 679 +++++++++++++++++++
 .../Apache.Arrow.Operations.Tests.csproj}          |   2 +-
 .../Json/VariantDecimalJsonTests.cs                | 117 ++++
 .../Json}/VariantJsonTests.cs                      |   5 +-
 .../Apache.Arrow.Variant.Benchmarks.csproj         |   1 +
 .../EncodingBenchmarks.cs                          |  13 +-
 .../Apache.Arrow.Variant.Tests.csproj              |   2 +
 .../VariantSqlDecimalTests.cs                      |  85 ---
 20 files changed, 1171 insertions(+), 838 deletions(-)

diff --git a/Apache.Arrow.Tests.slnf b/Apache.Arrow.Tests.slnf
index 92b57e6..0b16c89 100644
--- a/Apache.Arrow.Tests.slnf
+++ b/Apache.Arrow.Tests.slnf
@@ -7,7 +7,8 @@
       "test\\Apache.Arrow.Flight.Tests\\Apache.Arrow.Flight.Tests.csproj",
       "test\\Apache.Arrow.Flight.TestWeb\\Apache.Arrow.Flight.TestWeb.csproj",
       "test\\Apache.Arrow.Tests\\Apache.Arrow.Tests.csproj",
-      "test\\Apache.Arrow.Variant.Tests\\Apache.Arrow.Variant.Tests.csproj"
+      "test\\Apache.Arrow.Variant.Tests\\Apache.Arrow.Variant.Tests.csproj",
+      
"test\\Apache.Arrow.Operations.Tests\\Apache.Arrow.Operations.Tests.csproj"
     ]
   }
 }
diff --git a/Apache.Arrow.sln b/Apache.Arrow.sln
index f14c4d5..105eb0c 100644
--- a/Apache.Arrow.sln
+++ b/Apache.Arrow.sln
@@ -35,76 +35,236 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = 
"Apache.Arrow.Variant.Tests"
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = 
"Apache.Arrow.Variant.Benchmarks", 
"test\Apache.Arrow.Variant.Benchmarks\Apache.Arrow.Variant.Benchmarks.csproj", 
"{F3A8B7C6-D5E4-4321-9ABC-DEF012345678}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Apache.Arrow.Operations", 
"src\Apache.Arrow.Operations\Apache.Arrow.Operations.csproj", 
"{B4799C3C-179F-470F-A8A4-8A75D172E3E1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = 
"Apache.Arrow.Operations.Tests", 
"test\Apache.Arrow.Operations.Tests\Apache.Arrow.Operations.Tests.csproj", 
"{73EBE132-AE05-4C32-9525-515F4768156B}"
+EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Debug|Any CPU = Debug|Any CPU
+               Debug|x64 = Debug|x64
+               Debug|x86 = Debug|x86
                Release|Any CPU = Release|Any CPU
+               Release|x64 = Release|x64
+               Release|x86 = Release|x86
        EndGlobalSection
        GlobalSection(ProjectConfigurationPlatforms) = postSolution
                {BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Debug|Any CPU.ActiveCfg 
= Debug|Any CPU
                {BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
+               {BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Debug|x64.ActiveCfg = 
Debug|Any CPU
+               {BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Debug|x64.Build.0 = 
Debug|Any CPU
+               {BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Debug|x86.ActiveCfg = 
Debug|Any CPU
+               {BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Debug|x86.Build.0 = 
Debug|Any CPU
                {BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
                {BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Release|Any CPU.Build.0 
= Release|Any CPU
+               {BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Release|x64.ActiveCfg = 
Release|Any CPU
+               {BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Release|x64.Build.0 = 
Release|Any CPU
+               {BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Release|x86.ActiveCfg = 
Release|Any CPU
+               {BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Release|x86.Build.0 = 
Release|Any CPU
                {9CCEC01B-E67A-4726-BE72-7B514F76163F}.Debug|Any CPU.ActiveCfg 
= Debug|Any CPU
                {9CCEC01B-E67A-4726-BE72-7B514F76163F}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
+               {9CCEC01B-E67A-4726-BE72-7B514F76163F}.Debug|x64.ActiveCfg = 
Debug|Any CPU
+               {9CCEC01B-E67A-4726-BE72-7B514F76163F}.Debug|x64.Build.0 = 
Debug|Any CPU
+               {9CCEC01B-E67A-4726-BE72-7B514F76163F}.Debug|x86.ActiveCfg = 
Debug|Any CPU
+               {9CCEC01B-E67A-4726-BE72-7B514F76163F}.Debug|x86.Build.0 = 
Debug|Any CPU
                {9CCEC01B-E67A-4726-BE72-7B514F76163F}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
                {9CCEC01B-E67A-4726-BE72-7B514F76163F}.Release|Any CPU.Build.0 
= Release|Any CPU
+               {9CCEC01B-E67A-4726-BE72-7B514F76163F}.Release|x64.ActiveCfg = 
Release|Any CPU
+               {9CCEC01B-E67A-4726-BE72-7B514F76163F}.Release|x64.Build.0 = 
Release|Any CPU
+               {9CCEC01B-E67A-4726-BE72-7B514F76163F}.Release|x86.ActiveCfg = 
Release|Any CPU
+               {9CCEC01B-E67A-4726-BE72-7B514F76163F}.Release|x86.Build.0 = 
Release|Any CPU
                {742DF47D-77C5-4B84-9E0C-69645F1161EA}.Debug|Any CPU.ActiveCfg 
= Debug|Any CPU
                {742DF47D-77C5-4B84-9E0C-69645F1161EA}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
+               {742DF47D-77C5-4B84-9E0C-69645F1161EA}.Debug|x64.ActiveCfg = 
Debug|Any CPU
+               {742DF47D-77C5-4B84-9E0C-69645F1161EA}.Debug|x64.Build.0 = 
Debug|Any CPU
+               {742DF47D-77C5-4B84-9E0C-69645F1161EA}.Debug|x86.ActiveCfg = 
Debug|Any CPU
+               {742DF47D-77C5-4B84-9E0C-69645F1161EA}.Debug|x86.Build.0 = 
Debug|Any CPU
                {742DF47D-77C5-4B84-9E0C-69645F1161EA}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
                {742DF47D-77C5-4B84-9E0C-69645F1161EA}.Release|Any CPU.Build.0 
= Release|Any CPU
+               {742DF47D-77C5-4B84-9E0C-69645F1161EA}.Release|x64.ActiveCfg = 
Release|Any CPU
+               {742DF47D-77C5-4B84-9E0C-69645F1161EA}.Release|x64.Build.0 = 
Release|Any CPU
+               {742DF47D-77C5-4B84-9E0C-69645F1161EA}.Release|x86.ActiveCfg = 
Release|Any CPU
+               {742DF47D-77C5-4B84-9E0C-69645F1161EA}.Release|x86.Build.0 = 
Release|Any CPU
                {D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Debug|Any CPU.ActiveCfg 
= Debug|Any CPU
                {D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
+               {D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Debug|x64.ActiveCfg = 
Debug|Any CPU
+               {D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Debug|x64.Build.0 = 
Debug|Any CPU
+               {D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Debug|x86.ActiveCfg = 
Debug|Any CPU
+               {D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Debug|x86.Build.0 = 
Debug|Any CPU
                {D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
                {D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Release|Any CPU.Build.0 
= Release|Any CPU
+               {D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Release|x64.ActiveCfg = 
Release|Any CPU
+               {D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Release|x64.Build.0 = 
Release|Any CPU
+               {D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Release|x86.ActiveCfg = 
Release|Any CPU
+               {D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Release|x86.Build.0 = 
Release|Any CPU
                {058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Debug|Any CPU.ActiveCfg 
= Debug|Any CPU
                {058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
+               {058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Debug|x64.ActiveCfg = 
Debug|Any CPU
+               {058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Debug|x64.Build.0 = 
Debug|Any CPU
+               {058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Debug|x86.ActiveCfg = 
Debug|Any CPU
+               {058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Debug|x86.Build.0 = 
Debug|Any CPU
                {058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
                {058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Release|Any CPU.Build.0 
= Release|Any CPU
+               {058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Release|x64.ActiveCfg = 
Release|Any CPU
+               {058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Release|x64.Build.0 = 
Release|Any CPU
+               {058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Release|x86.ActiveCfg = 
Release|Any CPU
+               {058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Release|x86.Build.0 = 
Release|Any CPU
                {2490AA1E-DDA4-4069-B065-79A4897B0582}.Debug|Any CPU.ActiveCfg 
= Debug|Any CPU
                {2490AA1E-DDA4-4069-B065-79A4897B0582}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
+               {2490AA1E-DDA4-4069-B065-79A4897B0582}.Debug|x64.ActiveCfg = 
Debug|Any CPU
+               {2490AA1E-DDA4-4069-B065-79A4897B0582}.Debug|x64.Build.0 = 
Debug|Any CPU
+               {2490AA1E-DDA4-4069-B065-79A4897B0582}.Debug|x86.ActiveCfg = 
Debug|Any CPU
+               {2490AA1E-DDA4-4069-B065-79A4897B0582}.Debug|x86.Build.0 = 
Debug|Any CPU
                {2490AA1E-DDA4-4069-B065-79A4897B0582}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
                {2490AA1E-DDA4-4069-B065-79A4897B0582}.Release|Any CPU.Build.0 
= Release|Any CPU
+               {2490AA1E-DDA4-4069-B065-79A4897B0582}.Release|x64.ActiveCfg = 
Release|Any CPU
+               {2490AA1E-DDA4-4069-B065-79A4897B0582}.Release|x64.Build.0 = 
Release|Any CPU
+               {2490AA1E-DDA4-4069-B065-79A4897B0582}.Release|x86.ActiveCfg = 
Release|Any CPU
+               {2490AA1E-DDA4-4069-B065-79A4897B0582}.Release|x86.Build.0 = 
Release|Any CPU
                {E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Debug|Any CPU.ActiveCfg 
= Debug|Any CPU
                {E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
+               {E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Debug|x64.ActiveCfg = 
Debug|Any CPU
+               {E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Debug|x64.Build.0 = 
Debug|Any CPU
+               {E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Debug|x86.ActiveCfg = 
Debug|Any CPU
+               {E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Debug|x86.Build.0 = 
Debug|Any CPU
                {E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
                {E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Release|Any CPU.Build.0 
= Release|Any CPU
+               {E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Release|x64.ActiveCfg = 
Release|Any CPU
+               {E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Release|x64.Build.0 = 
Release|Any CPU
+               {E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Release|x86.ActiveCfg = 
Release|Any CPU
+               {E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Release|x86.Build.0 = 
Release|Any CPU
                {B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Debug|Any CPU.ActiveCfg 
= Debug|Any CPU
                {B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
+               {B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Debug|x64.ActiveCfg = 
Debug|Any CPU
+               {B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Debug|x64.Build.0 = 
Debug|Any CPU
+               {B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Debug|x86.ActiveCfg = 
Debug|Any CPU
+               {B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Debug|x86.Build.0 = 
Debug|Any CPU
                {B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
                {B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Release|Any CPU.Build.0 
= Release|Any CPU
+               {B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Release|x64.ActiveCfg = 
Release|Any CPU
+               {B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Release|x64.Build.0 = 
Release|Any CPU
+               {B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Release|x86.ActiveCfg = 
Release|Any CPU
+               {B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Release|x86.Build.0 = 
Release|Any CPU
                {5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Debug|Any CPU.ActiveCfg 
= Debug|Any CPU
                {5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
+               {5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Debug|x64.ActiveCfg = 
Debug|Any CPU
+               {5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Debug|x64.Build.0 = 
Debug|Any CPU
+               {5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Debug|x86.ActiveCfg = 
Debug|Any CPU
+               {5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Debug|x86.Build.0 = 
Debug|Any CPU
                {5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
                {5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Release|Any CPU.Build.0 
= Release|Any CPU
+               {5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Release|x64.ActiveCfg = 
Release|Any CPU
+               {5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Release|x64.Build.0 = 
Release|Any CPU
+               {5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Release|x86.ActiveCfg = 
Release|Any CPU
+               {5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Release|x86.Build.0 = 
Release|Any CPU
                {DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Debug|Any CPU.ActiveCfg 
= Debug|Any CPU
                {DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
+               {DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Debug|x64.ActiveCfg = 
Debug|Any CPU
+               {DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Debug|x64.Build.0 = 
Debug|Any CPU
+               {DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Debug|x86.ActiveCfg = 
Debug|Any CPU
+               {DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Debug|x86.Build.0 = 
Debug|Any CPU
                {DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
                {DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Release|Any CPU.Build.0 
= Release|Any CPU
+               {DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Release|x64.ActiveCfg = 
Release|Any CPU
+               {DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Release|x64.Build.0 = 
Release|Any CPU
+               {DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Release|x86.ActiveCfg = 
Release|Any CPU
+               {DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Release|x86.Build.0 = 
Release|Any CPU
                {2ADE087A-B424-4895-8CC5-10170D10BA62}.Debug|Any CPU.ActiveCfg 
= Debug|Any CPU
                {2ADE087A-B424-4895-8CC5-10170D10BA62}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
+               {2ADE087A-B424-4895-8CC5-10170D10BA62}.Debug|x64.ActiveCfg = 
Debug|Any CPU
+               {2ADE087A-B424-4895-8CC5-10170D10BA62}.Debug|x64.Build.0 = 
Debug|Any CPU
+               {2ADE087A-B424-4895-8CC5-10170D10BA62}.Debug|x86.ActiveCfg = 
Debug|Any CPU
+               {2ADE087A-B424-4895-8CC5-10170D10BA62}.Debug|x86.Build.0 = 
Debug|Any CPU
                {2ADE087A-B424-4895-8CC5-10170D10BA62}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
                {2ADE087A-B424-4895-8CC5-10170D10BA62}.Release|Any CPU.Build.0 
= Release|Any CPU
+               {2ADE087A-B424-4895-8CC5-10170D10BA62}.Release|x64.ActiveCfg = 
Release|Any CPU
+               {2ADE087A-B424-4895-8CC5-10170D10BA62}.Release|x64.Build.0 = 
Release|Any CPU
+               {2ADE087A-B424-4895-8CC5-10170D10BA62}.Release|x86.ActiveCfg = 
Release|Any CPU
+               {2ADE087A-B424-4895-8CC5-10170D10BA62}.Release|x86.Build.0 = 
Release|Any CPU
                {7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Debug|Any CPU.ActiveCfg 
= Debug|Any CPU
                {7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
+               {7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Debug|x64.ActiveCfg = 
Debug|Any CPU
+               {7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Debug|x64.Build.0 = 
Debug|Any CPU
+               {7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Debug|x86.ActiveCfg = 
Debug|Any CPU
+               {7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Debug|x86.Build.0 = 
Debug|Any CPU
                {7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
                {7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Release|Any CPU.Build.0 
= Release|Any CPU
+               {7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Release|x64.ActiveCfg = 
Release|Any CPU
+               {7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Release|x64.Build.0 = 
Release|Any CPU
+               {7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Release|x86.ActiveCfg = 
Release|Any CPU
+               {7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Release|x86.Build.0 = 
Release|Any CPU
                {E8264B7F-B680-4A55-939B-85DB628164BB}.Debug|Any CPU.ActiveCfg 
= Debug|Any CPU
                {E8264B7F-B680-4A55-939B-85DB628164BB}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
+               {E8264B7F-B680-4A55-939B-85DB628164BB}.Debug|x64.ActiveCfg = 
Debug|Any CPU
+               {E8264B7F-B680-4A55-939B-85DB628164BB}.Debug|x64.Build.0 = 
Debug|Any CPU
+               {E8264B7F-B680-4A55-939B-85DB628164BB}.Debug|x86.ActiveCfg = 
Debug|Any CPU
+               {E8264B7F-B680-4A55-939B-85DB628164BB}.Debug|x86.Build.0 = 
Debug|Any CPU
                {E8264B7F-B680-4A55-939B-85DB628164BB}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
                {E8264B7F-B680-4A55-939B-85DB628164BB}.Release|Any CPU.Build.0 
= Release|Any CPU
+               {E8264B7F-B680-4A55-939B-85DB628164BB}.Release|x64.ActiveCfg = 
Release|Any CPU
+               {E8264B7F-B680-4A55-939B-85DB628164BB}.Release|x64.Build.0 = 
Release|Any CPU
+               {E8264B7F-B680-4A55-939B-85DB628164BB}.Release|x86.ActiveCfg = 
Release|Any CPU
+               {E8264B7F-B680-4A55-939B-85DB628164BB}.Release|x86.Build.0 = 
Release|Any CPU
                {EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Debug|Any CPU.ActiveCfg 
= Debug|Any CPU
                {EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
+               {EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Debug|x64.ActiveCfg = 
Debug|Any CPU
+               {EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Debug|x64.Build.0 = 
Debug|Any CPU
+               {EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Debug|x86.ActiveCfg = 
Debug|Any CPU
+               {EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Debug|x86.Build.0 = 
Debug|Any CPU
                {EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
                {EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Release|Any CPU.Build.0 
= Release|Any CPU
+               {EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Release|x64.ActiveCfg = 
Release|Any CPU
+               {EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Release|x64.Build.0 = 
Release|Any CPU
+               {EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Release|x86.ActiveCfg = 
Release|Any CPU
+               {EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Release|x86.Build.0 = 
Release|Any CPU
                {0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Debug|Any CPU.ActiveCfg 
= Debug|Any CPU
                {0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
+               {0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Debug|x64.ActiveCfg = 
Debug|Any CPU
+               {0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Debug|x64.Build.0 = 
Debug|Any CPU
+               {0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Debug|x86.ActiveCfg = 
Debug|Any CPU
+               {0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Debug|x86.Build.0 = 
Debug|Any CPU
                {0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
                {0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Release|Any CPU.Build.0 
= Release|Any CPU
+               {0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Release|x64.ActiveCfg = 
Release|Any CPU
+               {0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Release|x64.Build.0 = 
Release|Any CPU
+               {0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Release|x86.ActiveCfg = 
Release|Any CPU
+               {0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Release|x86.Build.0 = 
Release|Any CPU
                {F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Debug|Any CPU.ActiveCfg 
= Debug|Any CPU
                {F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
+               {F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Debug|x64.ActiveCfg = 
Debug|Any CPU
+               {F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Debug|x64.Build.0 = 
Debug|Any CPU
+               {F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Debug|x86.ActiveCfg = 
Debug|Any CPU
+               {F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Debug|x86.Build.0 = 
Debug|Any CPU
                {F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
                {F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Release|Any CPU.Build.0 
= Release|Any CPU
+               {F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Release|x64.ActiveCfg = 
Release|Any CPU
+               {F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Release|x64.Build.0 = 
Release|Any CPU
+               {F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Release|x86.ActiveCfg = 
Release|Any CPU
+               {F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Release|x86.Build.0 = 
Release|Any CPU
+               {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Debug|Any CPU.ActiveCfg 
= Debug|Any CPU
+               {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
+               {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Debug|x64.ActiveCfg = 
Debug|Any CPU
+               {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Debug|x64.Build.0 = 
Debug|Any CPU
+               {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Debug|x86.ActiveCfg = 
Debug|Any CPU
+               {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Debug|x86.Build.0 = 
Debug|Any CPU
+               {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
+               {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Release|Any CPU.Build.0 
= Release|Any CPU
+               {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Release|x64.ActiveCfg = 
Release|Any CPU
+               {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Release|x64.Build.0 = 
Release|Any CPU
+               {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Release|x86.ActiveCfg = 
Release|Any CPU
+               {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Release|x86.Build.0 = 
Release|Any CPU
+               {73EBE132-AE05-4C32-9525-515F4768156B}.Debug|Any CPU.ActiveCfg 
= Debug|Any CPU
+               {73EBE132-AE05-4C32-9525-515F4768156B}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
+               {73EBE132-AE05-4C32-9525-515F4768156B}.Debug|x64.ActiveCfg = 
Debug|Any CPU
+               {73EBE132-AE05-4C32-9525-515F4768156B}.Debug|x64.Build.0 = 
Debug|Any CPU
+               {73EBE132-AE05-4C32-9525-515F4768156B}.Debug|x86.ActiveCfg = 
Debug|Any CPU
+               {73EBE132-AE05-4C32-9525-515F4768156B}.Debug|x86.Build.0 = 
Debug|Any CPU
+               {73EBE132-AE05-4C32-9525-515F4768156B}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
+               {73EBE132-AE05-4C32-9525-515F4768156B}.Release|Any CPU.Build.0 
= Release|Any CPU
+               {73EBE132-AE05-4C32-9525-515F4768156B}.Release|x64.ActiveCfg = 
Release|Any CPU
+               {73EBE132-AE05-4C32-9525-515F4768156B}.Release|x64.Build.0 = 
Release|Any CPU
+               {73EBE132-AE05-4C32-9525-515F4768156B}.Release|x86.ActiveCfg = 
Release|Any CPU
+               {73EBE132-AE05-4C32-9525-515F4768156B}.Release|x86.Build.0 = 
Release|Any CPU
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
diff --git a/dev/release/verify_rc.sh b/dev/release/verify_rc.sh
index bbb5f4b..8fcad5e 100755
--- a/dev/release/verify_rc.sh
+++ b/dev/release/verify_rc.sh
@@ -184,6 +184,7 @@ test_binary_distribution() {
   reference_package "Apache.Arrow.Compression" "Apache.Arrow.Compression.Tests"
   reference_package "Apache.Arrow.Flight.Sql" "Apache.Arrow.Flight.Sql.Tests" 
"Apache.Arrow.Flight.TestWeb"
   reference_package "Apache.Arrow.Flight.AspNetCore" 
"Apache.Arrow.Flight.TestWeb"
+  reference_package "Apache.Arrow.Operations" "Apache.Arrow.Operations.Tests" 
"Apache.Arrow.Variant.Tests"
   reference_package "Apache.Arrow.Variant" "Apache.Arrow.Variant.Tests"
 
   # Move src directory to ensure we are only testing against built packages
diff --git a/src/Apache.Arrow.Variant/Apache.Arrow.Variant.csproj 
b/src/Apache.Arrow.Operations/Apache.Arrow.Operations.csproj
similarity index 63%
copy from src/Apache.Arrow.Variant/Apache.Arrow.Variant.csproj
copy to src/Apache.Arrow.Operations/Apache.Arrow.Operations.csproj
index 181dff9..8cc1c4c 100644
--- a/src/Apache.Arrow.Variant/Apache.Arrow.Variant.csproj
+++ b/src/Apache.Arrow.Operations/Apache.Arrow.Operations.csproj
@@ -4,14 +4,12 @@
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
 
-    <Description>Apache Arrow Variant encoding support for .NET. Provides 
readers, writers, and object model for the Parquet Variant binary format for 
semi-structured data.</Description>
+    <Description>Format-specific codecs for the Apache Arrow Variant binary 
format, including JSON reader/writer.</Description>
   </PropertyGroup>
 
   <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETStandard'">
     <PackageReference Include="System.Buffers" />
-    <PackageReference Include="System.Data.Common" />
     <PackageReference Include="System.Memory" />
-    <PackageReference Include="System.Runtime.CompilerServices.Unsafe" />
   </ItemGroup>
 
   <ItemGroup>
@@ -19,4 +17,8 @@
     <PackageReference Include="Microsoft.SourceLink.GitHub" 
PrivateAssets="All" />
   </ItemGroup>
 
+  <ItemGroup>
+    <ProjectReference 
Include="..\Apache.Arrow.Variant\Apache.Arrow.Variant.csproj" />
+  </ItemGroup>
+
 </Project>
diff --git a/src/Apache.Arrow.Variant/Json/VariantJsonConverter.cs 
b/src/Apache.Arrow.Operations/Json/VariantJsonConverter.cs
similarity index 99%
rename from src/Apache.Arrow.Variant/Json/VariantJsonConverter.cs
rename to src/Apache.Arrow.Operations/Json/VariantJsonConverter.cs
index 73494bd..b1806a5 100644
--- a/src/Apache.Arrow.Variant/Json/VariantJsonConverter.cs
+++ b/src/Apache.Arrow.Operations/Json/VariantJsonConverter.cs
@@ -15,11 +15,11 @@
 
 using System;
 using System.Collections.Generic;
-using System.Data.SqlTypes;
 using System.Text.Json;
 using System.Text.Json.Serialization;
+using Apache.Arrow.Variant;
 
-namespace Apache.Arrow.Variant.Json
+namespace Apache.Arrow.Operations.Json
 {
     /// <summary>
     /// A <see cref="JsonConverter{T}"/> for <see cref="VariantValue"/>.
diff --git a/src/Apache.Arrow.Operations/Json/VariantJsonReader.cs 
b/src/Apache.Arrow.Operations/Json/VariantJsonReader.cs
new file mode 100644
index 0000000..9585008
--- /dev/null
+++ b/src/Apache.Arrow.Operations/Json/VariantJsonReader.cs
@@ -0,0 +1,152 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
+using System.Text;
+using System.Text.Json;
+using Apache.Arrow.Variant;
+
+namespace Apache.Arrow.Operations.Json
+{
+    /// <summary>
+    /// Parses a JSON string or UTF-8 bytes directly into variant binary format
+    /// (metadata + value byte arrays) without creating intermediate <see 
cref="VariantValue"/> objects.
+    /// </summary>
+    public static class VariantJsonReader
+    {
+        /// <summary>
+        /// Parses a JSON string into variant binary format.
+        /// </summary>
+        /// <param name="json">The JSON string to parse.</param>
+        /// <returns>A tuple of (metadata bytes, value bytes).</returns>
+        public static (byte[] Metadata, byte[] Value) Parse(string json)
+        {
+            byte[] utf8 = Encoding.UTF8.GetBytes(json);
+            return Parse(new ReadOnlySpan<byte>(utf8));
+        }
+
+        /// <summary>
+        /// Parses UTF-8 encoded JSON bytes into variant binary format.
+        /// </summary>
+        /// <param name="utf8Json">The UTF-8 encoded JSON bytes.</param>
+        /// <returns>A tuple of (metadata bytes, value bytes).</returns>
+        public static (byte[] Metadata, byte[] Value) Parse(ReadOnlySpan<byte> 
utf8Json)
+        {
+            // Pass 1: collect all field names into the metadata dictionary.
+            VariantMetadataBuilder metadataBuilder = new 
VariantMetadataBuilder();
+            Utf8JsonReader collector = new Utf8JsonReader(utf8Json);
+            collector.Read();
+            CollectFieldNames(ref collector, metadataBuilder);
+
+            byte[] metadata = metadataBuilder.Build(out int[] idRemap);
+
+            // Pass 2: stream values into a VariantValueWriter using the 
sorted field IDs.
+            Utf8JsonReader emitter = new Utf8JsonReader(utf8Json);
+            emitter.Read();
+            VariantValueWriter writer = new 
VariantValueWriter(metadataBuilder, idRemap);
+            WriteValue(ref emitter, writer);
+            return (metadata, writer.ToArray());
+        }
+
+        private static void CollectFieldNames(ref Utf8JsonReader reader, 
VariantMetadataBuilder builder)
+        {
+            switch (reader.TokenType)
+            {
+                case JsonTokenType.StartObject:
+                    while (reader.Read())
+                    {
+                        if (reader.TokenType == JsonTokenType.EndObject)
+                            return;
+                        builder.Add(reader.GetString());
+                        reader.Read();
+                        CollectFieldNames(ref reader, builder);
+                    }
+                    throw new JsonException("Unterminated JSON object.");
+
+                case JsonTokenType.StartArray:
+                    while (reader.Read())
+                    {
+                        if (reader.TokenType == JsonTokenType.EndArray)
+                            return;
+                        CollectFieldNames(ref reader, builder);
+                    }
+                    throw new JsonException("Unterminated JSON array.");
+
+                default:
+                    return;
+            }
+        }
+
+        private static void WriteValue(ref Utf8JsonReader reader, 
VariantValueWriter writer)
+        {
+            switch (reader.TokenType)
+            {
+                case JsonTokenType.Null:
+                    writer.WriteNull();
+                    return;
+
+                case JsonTokenType.True:
+                    writer.WriteBoolean(true);
+                    return;
+
+                case JsonTokenType.False:
+                    writer.WriteBoolean(false);
+                    return;
+
+                case JsonTokenType.Number:
+                    if (reader.TryGetInt64(out long longValue))
+                    {
+                        writer.WriteIntegerCompact(longValue);
+                    }
+                    else
+                    {
+                        writer.WriteDouble(reader.GetDouble());
+                    }
+                    return;
+
+                case JsonTokenType.String:
+                    writer.WriteString(reader.GetString());
+                    return;
+
+                case JsonTokenType.StartObject:
+                    writer.BeginObject();
+                    while (reader.Read())
+                    {
+                        if (reader.TokenType == JsonTokenType.EndObject)
+                            break;
+                        writer.WriteFieldName(reader.GetString());
+                        reader.Read();
+                        WriteValue(ref reader, writer);
+                    }
+                    writer.EndObject();
+                    return;
+
+                case JsonTokenType.StartArray:
+                    writer.BeginArray();
+                    while (reader.Read())
+                    {
+                        if (reader.TokenType == JsonTokenType.EndArray)
+                            break;
+                        WriteValue(ref reader, writer);
+                    }
+                    writer.EndArray();
+                    return;
+
+                default:
+                    throw new JsonException($"Unexpected JSON token type 
{reader.TokenType}.");
+            }
+        }
+    }
+}
diff --git a/src/Apache.Arrow.Variant/Json/VariantJsonWriter.cs 
b/src/Apache.Arrow.Operations/Json/VariantJsonWriter.cs
similarity index 99%
rename from src/Apache.Arrow.Variant/Json/VariantJsonWriter.cs
rename to src/Apache.Arrow.Operations/Json/VariantJsonWriter.cs
index 9287ceb..2f9e216 100644
--- a/src/Apache.Arrow.Variant/Json/VariantJsonWriter.cs
+++ b/src/Apache.Arrow.Operations/Json/VariantJsonWriter.cs
@@ -19,11 +19,11 @@ using System.Buffers;
 #else
 using System.IO;
 #endif
-using System.Data.SqlTypes;
 using System.Text;
 using System.Text.Json;
+using Apache.Arrow.Variant;
 
-namespace Apache.Arrow.Variant.Json
+namespace Apache.Arrow.Operations.Json
 {
     /// <summary>
     /// Writes variant binary data directly to JSON without creating 
intermediate
diff --git a/src/Apache.Arrow.Variant/Properties/AssemblyInfo.cs 
b/src/Apache.Arrow.Operations/Properties/AssemblyInfo.cs
similarity index 67%
copy from src/Apache.Arrow.Variant/Properties/AssemblyInfo.cs
copy to src/Apache.Arrow.Operations/Properties/AssemblyInfo.cs
index c990b57..423bbba 100644
--- a/src/Apache.Arrow.Variant/Properties/AssemblyInfo.cs
+++ b/src/Apache.Arrow.Operations/Properties/AssemblyInfo.cs
@@ -15,4 +15,4 @@
 
 using System.Runtime.CompilerServices;
 
-[assembly: InternalsVisibleTo("Apache.Arrow.Variant.Tests, 
PublicKey=0024000004800000940000000602000000240000525341310004000001000100e504183f6d470d6b67b6d19212be3e1f598f70c246a120194bc38130101d0c1853e4a0f2232cb12e37a7a90e707aabd38511dac4f25fcb0d691b2aa265900bf42de7f70468fc997551a40e1e0679b605aa2088a4a69e07c117e988f5b1738c570ee66997fba02485e7856a49eca5fd0706d09899b8312577cbb9034599fc92d4")]
+[assembly: InternalsVisibleTo("Apache.Arrow.Operations.Tests, 
PublicKey=0024000004800000940000000602000000240000525341310004000001000100e504183f6d470d6b67b6d19212be3e1f598f70c246a120194bc38130101d0c1853e4a0f2232cb12e37a7a90e707aabd38511dac4f25fcb0d691b2aa265900bf42de7f70468fc997551a40e1e0679b605aa2088a4a69e07c117e988f5b1738c570ee66997fba02485e7856a49eca5fd0706d09899b8312577cbb9034599fc92d4")]
diff --git a/src/Apache.Arrow.Variant/Apache.Arrow.Variant.csproj 
b/src/Apache.Arrow.Variant/Apache.Arrow.Variant.csproj
index 181dff9..17d7938 100644
--- a/src/Apache.Arrow.Variant/Apache.Arrow.Variant.csproj
+++ b/src/Apache.Arrow.Variant/Apache.Arrow.Variant.csproj
@@ -15,7 +15,6 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="System.Text.Json" />
     <PackageReference Include="Microsoft.SourceLink.GitHub" 
PrivateAssets="All" />
   </ItemGroup>
 
diff --git a/src/Apache.Arrow.Variant/Json/VariantJsonReader.cs 
b/src/Apache.Arrow.Variant/Json/VariantJsonReader.cs
deleted file mode 100644
index f1a8700..0000000
--- a/src/Apache.Arrow.Variant/Json/VariantJsonReader.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements. See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to You under the Apache License, Version 2.0
-// (the "License"); you may not use this file except in compliance with
-// the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-using System;
-using System.Text;
-
-namespace Apache.Arrow.Variant.Json
-{
-    /// <summary>
-    /// Parses a JSON string or UTF-8 bytes directly into variant binary format
-    /// (metadata + value byte arrays) without creating intermediate <see 
cref="VariantValue"/> objects.
-    /// </summary>
-    public static class VariantJsonReader
-    {
-        /// <summary>
-        /// Parses a JSON string into variant binary format.
-        /// </summary>
-        /// <param name="json">The JSON string to parse.</param>
-        /// <returns>A tuple of (metadata bytes, value bytes).</returns>
-        public static (byte[] Metadata, byte[] Value) Parse(string json)
-        {
-            byte[] utf8 = Encoding.UTF8.GetBytes(json);
-            return Parse(new ReadOnlySpan<byte>(utf8));
-        }
-
-        /// <summary>
-        /// Parses UTF-8 encoded JSON bytes into variant binary format.
-        /// </summary>
-        /// <param name="utf8Json">The UTF-8 encoded JSON bytes.</param>
-        /// <returns>A tuple of (metadata bytes, value bytes).</returns>
-        public static (byte[] Metadata, byte[] Value) Parse(ReadOnlySpan<byte> 
utf8Json)
-        {
-            VariantBuilder builder = new VariantBuilder();
-            return builder.EncodeFromJson(utf8Json);
-        }
-    }
-}
diff --git a/src/Apache.Arrow.Variant/Properties/AssemblyInfo.cs 
b/src/Apache.Arrow.Variant/Properties/AssemblyInfo.cs
index c990b57..6386c23 100644
--- a/src/Apache.Arrow.Variant/Properties/AssemblyInfo.cs
+++ b/src/Apache.Arrow.Variant/Properties/AssemblyInfo.cs
@@ -16,3 +16,4 @@
 using System.Runtime.CompilerServices;
 
 [assembly: InternalsVisibleTo("Apache.Arrow.Variant.Tests, 
PublicKey=0024000004800000940000000602000000240000525341310004000001000100e504183f6d470d6b67b6d19212be3e1f598f70c246a120194bc38130101d0c1853e4a0f2232cb12e37a7a90e707aabd38511dac4f25fcb0d691b2aa265900bf42de7f70468fc997551a40e1e0679b605aa2088a4a69e07c117e988f5b1738c570ee66997fba02485e7856a49eca5fd0706d09899b8312577cbb9034599fc92d4")]
+[assembly: InternalsVisibleTo("Apache.Arrow.Operations, 
PublicKey=0024000004800000940000000602000000240000525341310004000001000100e504183f6d470d6b67b6d19212be3e1f598f70c246a120194bc38130101d0c1853e4a0f2232cb12e37a7a90e707aabd38511dac4f25fcb0d691b2aa265900bf42de7f70468fc997551a40e1e0679b605aa2088a4a69e07c117e988f5b1738c570ee66997fba02485e7856a49eca5fd0706d09899b8312577cbb9034599fc92d4")]
diff --git a/src/Apache.Arrow.Variant/VariantBuilder.cs 
b/src/Apache.Arrow.Variant/VariantBuilder.cs
index 648b1d0..9e40120 100644
--- a/src/Apache.Arrow.Variant/VariantBuilder.cs
+++ b/src/Apache.Arrow.Variant/VariantBuilder.cs
@@ -14,12 +14,7 @@
 // limitations under the License.
 
 using System;
-using System.Buffers.Binary;
 using System.Collections.Generic;
-using System.Data.SqlTypes;
-using System.IO;
-using System.Text;
-using System.Text.Json;
 
 namespace Apache.Arrow.Variant
 {
@@ -29,38 +24,19 @@ namespace Apache.Arrow.Variant
     /// </summary>
     public sealed class VariantBuilder
     {
-        /// <summary>
-        /// Maximum number of offset entries (count + 1) to allocate on the 
stack.
-        /// 256 ints = 1024 bytes, well within safe stack limits.
-        /// </summary>
-        private const int StackAllocThreshold = 256;
-
-#if !NET8_0_OR_GREATER
-        private byte[] _buffer;
-#endif
-        private readonly Stack<MemoryStream> _streamPool = new 
Stack<MemoryStream>();
-
         /// <summary>
         /// Encodes a <see cref="VariantValue"/> to the variant binary format.
         /// </summary>
         /// <returns>A tuple of (metadata bytes, value bytes).</returns>
         public (byte[] Metadata, byte[] Value) Encode(VariantValue variant)
         {
-            EnsureBuffer();
-
-            // Phase 1: collect all field names recursively.
             VariantMetadataBuilder metadataBuilder = new 
VariantMetadataBuilder();
             CollectFieldNames(variant, metadataBuilder);
-
-            // Phase 2: build sorted metadata + get the ID remap.
             byte[] metadata = metadataBuilder.Build(out int[] idRemap);
 
-            // Phase 3: encode the value.
-            using (MemoryStream ms = new MemoryStream())
-            {
-                WriteValue(variant, metadataBuilder, idRemap, ms);
-                return (metadata, ms.ToArray());
-            }
+            VariantValueWriter writer = new 
VariantValueWriter(metadataBuilder, idRemap);
+            WriteValue(writer, variant);
+            return (metadata, writer.ToArray());
         }
 
         private static void CollectFieldNames(VariantValue variant, 
VariantMetadataBuilder builder)
@@ -82,724 +58,102 @@ namespace Apache.Arrow.Variant
             }
         }
 
-        private void WriteValue(VariantValue variant, VariantMetadataBuilder 
metadataBuilder, int[] idRemap, MemoryStream ms)
+        private static void WriteValue(VariantValueWriter writer, VariantValue 
variant)
         {
             if (variant.IsNull)
             {
-                
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.NullType));
+                writer.WriteNull();
                 return;
             }
 
             if (variant.IsBoolean)
             {
-                VariantPrimitiveType pt = variant.AsBoolean()
-                    ? VariantPrimitiveType.BooleanTrue
-                    : VariantPrimitiveType.BooleanFalse;
-                ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(pt));
+                writer.WriteBoolean(variant.AsBoolean());
                 return;
             }
 
             if (variant.IsObject)
             {
-                WriteObject(variant, metadataBuilder, idRemap, ms);
+                writer.BeginObject();
+                foreach (KeyValuePair<string, VariantValue> field in 
variant.AsObject())
+                {
+                    writer.WriteFieldName(field.Key);
+                    WriteValue(writer, field.Value);
+                }
+                writer.EndObject();
                 return;
             }
 
             if (variant.IsArray)
             {
-                WriteArray(variant, metadataBuilder, idRemap, ms);
+                writer.BeginArray();
+                foreach (VariantValue element in variant.AsArray())
+                {
+                    WriteValue(writer, element);
+                }
+                writer.EndArray();
                 return;
             }
 
-            // Primitive types
             switch (variant.PrimitiveType)
             {
                 case VariantPrimitiveType.Int8:
-                    
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int8));
-                    ms.WriteByte((byte)variant.AsInt8());
+                    writer.WriteInt8(variant.AsInt8());
                     break;
-
                 case VariantPrimitiveType.Int16:
-                    
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int16));
-                    WriteInt16(ms, variant.AsInt16());
+                    writer.WriteInt16(variant.AsInt16());
                     break;
-
                 case VariantPrimitiveType.Int32:
-                    
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int32));
-                    WriteInt32(ms, variant.AsInt32());
+                    writer.WriteInt32(variant.AsInt32());
                     break;
-
                 case VariantPrimitiveType.Int64:
-                    
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int64));
-                    WriteInt64(ms, variant.AsInt64());
+                    writer.WriteInt64(variant.AsInt64());
                     break;
-
                 case VariantPrimitiveType.Float:
-                    
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Float));
-                    WriteFloat(ms, variant.AsFloat());
+                    writer.WriteFloat(variant.AsFloat());
                     break;
-
                 case VariantPrimitiveType.Double:
-                    
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Double));
-                    WriteDouble(ms, variant.AsDouble());
+                    writer.WriteDouble(variant.AsDouble());
                     break;
-
                 case VariantPrimitiveType.Decimal4:
-                    WriteDecimal4(ms, variant.AsDecimal());
+                    writer.WriteDecimal4(variant.AsDecimal());
                     break;
-
                 case VariantPrimitiveType.Decimal8:
-                    WriteDecimal8(ms, variant.AsDecimal());
+                    writer.WriteDecimal8(variant.AsDecimal());
                     break;
-
                 case VariantPrimitiveType.Decimal16:
-                    SqlDecimal sd = variant.AsSqlDecimal();
-                    WriteSqlDecimal16(ms, sd);
+                    writer.WriteDecimal16(variant.AsSqlDecimal());
                     break;
-
                 case VariantPrimitiveType.Date:
-                    
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Date));
-                    WriteInt32(ms, variant.AsDateDays());
+                    writer.WriteDateDays(variant.AsDateDays());
                     break;
-
                 case VariantPrimitiveType.Timestamp:
-                    
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Timestamp));
-                    WriteInt64(ms, variant.AsTimestampMicros());
+                    writer.WriteTimestampMicros(variant.AsTimestampMicros());
                     break;
-
                 case VariantPrimitiveType.TimestampNtz:
-                    
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.TimestampNtz));
-                    WriteInt64(ms, variant.AsTimestampNtzMicros());
+                    
writer.WriteTimestampNtzMicros(variant.AsTimestampNtzMicros());
                     break;
-
                 case VariantPrimitiveType.TimeNtz:
-                    
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.TimeNtz));
-                    WriteInt64(ms, variant.AsTimeNtzMicros());
+                    writer.WriteTimeNtzMicros(variant.AsTimeNtzMicros());
                     break;
-
                 case VariantPrimitiveType.TimestampTzNanos:
-                    
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.TimestampTzNanos));
-                    WriteInt64(ms, variant.AsTimestampTzNanos());
+                    writer.WriteTimestampTzNanos(variant.AsTimestampTzNanos());
                     break;
-
                 case VariantPrimitiveType.TimestampNtzNanos:
-                    
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.TimestampNtzNanos));
-                    WriteInt64(ms, variant.AsTimestampNtzNanos());
+                    
writer.WriteTimestampNtzNanos(variant.AsTimestampNtzNanos());
                     break;
-
                 case VariantPrimitiveType.String:
-                    WriteString(ms, variant.AsString());
+                    writer.WriteString(variant.AsString());
                     break;
-
                 case VariantPrimitiveType.Binary:
-                    WriteBinary(ms, variant.AsBinary());
+                    writer.WriteBinary(variant.AsBinary());
                     break;
-
                 case VariantPrimitiveType.Uuid:
-                    WriteUuid(ms, variant.AsUuid());
+                    writer.WriteUuid(variant.AsUuid());
                     break;
-
                 default:
                     throw new NotSupportedException($"Unsupported primitive 
type: {variant.PrimitiveType}");
             }
         }
-
-        private void WriteString(MemoryStream ms, string value)
-        {
-            int byteCount = Encoding.UTF8.GetByteCount(value);
-            if (byteCount <= 63)
-            {
-                // Short string
-                
ms.WriteByte(VariantEncodingHelper.MakeShortStringHeader(byteCount));
-            }
-            else
-            {
-                // Long string (primitive type 16)
-                
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.String));
-                WriteInt32(ms, byteCount);
-            }
-
-            // Encode UTF-8 directly into the MemoryStream's buffer to avoid a 
temporary byte[] allocation.
-            int dataPos = (int)ms.Position;
-            int needed = dataPos + byteCount;
-            if (needed > ms.Length)
-                ms.SetLength(needed);
-            Encoding.UTF8.GetBytes(value, 0, value.Length, ms.GetBuffer(), 
dataPos);
-            ms.Position = needed;
-        }
-
-        private void WriteBinary(MemoryStream ms, byte[] data)
-        {
-            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Binary));
-            WriteInt32(ms, data.Length);
-            ms.Write(data, 0, data.Length);
-        }
-
-        private void WriteUuid(MemoryStream ms, Guid guid)
-        {
-            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Uuid));
-#if NET8_0_OR_GREATER
-            Span<byte> buf = stackalloc byte[16];
-            guid.TryWriteBytes(buf, bigEndian: true, out int _);
-            ms.Write(buf);
-#else
-            byte[] native = guid.ToByteArray();
-            // Convert from .NET mixed-endian to big-endian (RFC 4122)
-            _buffer[0] = native[3]; _buffer[1] = native[2]; _buffer[2] = 
native[1]; _buffer[3] = native[0];
-            _buffer[4] = native[5]; _buffer[5] = native[4];
-            _buffer[6] = native[7]; _buffer[7] = native[6];
-            Buffer.BlockCopy(native, 8, _buffer, 8, 8);
-            ms.Write(_buffer, 0, 16);
-#endif
-        }
-
-        private void WriteDecimal4(MemoryStream ms, decimal value)
-        {
-            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Decimal4));
-#if NET8_0_OR_GREATER
-            Span<int> bits = stackalloc int[4];
-            decimal.GetBits(value, bits);
-#else
-            int[] bits = decimal.GetBits(value);
-#endif
-            byte scale = (byte)((bits[3] >> 16) & 0x7F);
-            bool negative = (bits[3] & unchecked((int)0x80000000)) != 0;
-            int unscaled = bits[0];
-            if (negative) unscaled = -unscaled;
-            ms.WriteByte(scale);
-            WriteInt32(ms, unscaled);
-        }
-
-        private void WriteDecimal8(MemoryStream ms, decimal value)
-        {
-            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Decimal8));
-#if NET8_0_OR_GREATER
-            Span<int> bits = stackalloc int[4];
-            decimal.GetBits(value, bits);
-#else
-            int[] bits = decimal.GetBits(value);
-#endif
-            byte scale = (byte)((bits[3] >> 16) & 0x7F);
-            bool negative = (bits[3] & unchecked((int)0x80000000)) != 0;
-            long unscaled = ((long)bits[1] << 32) | (uint)bits[0];
-            if (negative) unscaled = -unscaled;
-            ms.WriteByte(scale);
-            WriteInt64(ms, unscaled);
-        }
-
-        private void WriteSqlDecimal16(MemoryStream ms, SqlDecimal value)
-        {
-            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Decimal16));
-
-            bool positive = value.IsPositive;
-            byte scale = (byte)value.Scale;
-            int[] data = value.Data;
-
-            // SqlDecimal.Data: [0]=least-significant, [3]=most-significant
-            // Convert to lo/hi longs (little-endian)
-            long lo = ((long)(uint)data[1] << 32) | (uint)data[0];
-            long hi = ((long)(uint)data[3] << 32) | (uint)data[2];
-
-            if (!positive)
-            {
-                // Two's complement negate 128-bit
-                lo = ~lo;
-                hi = ~hi;
-                ulong uLo = (ulong)lo + 1;
-                if (uLo == 0) hi++;
-                lo = (long)uLo;
-            }
-
-            ms.WriteByte(scale);
-            WriteInt64(ms, lo);
-            WriteInt64(ms, hi);
-        }
-
-        private void WriteObject(VariantValue variant, VariantMetadataBuilder 
metadataBuilder, int[] idRemap, MemoryStream ms)
-        {
-            IReadOnlyDictionary<string, VariantValue> fields = 
variant.AsObject();
-            int fieldCount = fields.Count;
-
-            // Build sorted array of (sortedFieldId, value) pairs.
-            KeyValuePair<int, VariantValue>[] sortedFields = new 
KeyValuePair<int, VariantValue>[fieldCount];
-            int fieldIndex = 0;
-            foreach (KeyValuePair<string, VariantValue> field in fields)
-            {
-                int originalId = metadataBuilder.GetId(field.Key);
-                int sortedId = idRemap[originalId];
-                sortedFields[fieldIndex++] = new KeyValuePair<int, 
VariantValue>(sortedId, field.Value);
-            }
-            Array.Sort(sortedFields, static (a, b) => a.Key.CompareTo(b.Key));
-
-            // Encode all field values into a single stream, recording offsets.
-            MemoryStream valuesMs = RentStream();
-            int offsetCount = fieldCount + 1;
-            Span<int> offsets = offsetCount <= StackAllocThreshold
-                ? stackalloc int[offsetCount]
-                : new int[offsetCount];
-            offsets[0] = 0;
-            for (int i = 0; i < fieldCount; i++)
-            {
-                WriteValue(sortedFields[i].Value, metadataBuilder, idRemap, 
valuesMs);
-                offsets[i + 1] = (int)valuesMs.Position;
-            }
-
-            // Determine sizes. sortedFields is sorted by Key, so max is the 
last element.
-            int maxFieldId = fieldCount > 0 ? sortedFields[fieldCount - 1].Key 
: 0;
-
-            int fieldIdSize = fieldCount > 0 ? 
VariantEncodingHelper.ByteWidthForValue(maxFieldId) : 1;
-            int offsetSize = 
VariantEncodingHelper.ByteWidthForValue(Math.Max(1, offsets[fieldCount]));
-            bool isLarge = fieldCount > 255;
-
-            // Write header.
-            ms.WriteByte(VariantEncodingHelper.MakeObjectHeader(fieldIdSize, 
offsetSize, isLarge));
-
-            // Write field count.
-            if (isLarge)
-            {
-                WriteInt32(ms, fieldCount);
-            }
-            else
-            {
-                ms.WriteByte((byte)fieldCount);
-            }
-
-            // Write field IDs.
-            for (int i = 0; i < fieldCount; i++)
-            {
-                WriteSmallInt(ms, sortedFields[i].Key, fieldIdSize);
-            }
-
-            // Write offsets.
-            for (int i = 0; i <= fieldCount; i++)
-            {
-                WriteSmallInt(ms, offsets[i], offsetSize);
-            }
-
-            // Write field values from the shared buffer.
-            ms.Write(valuesMs.GetBuffer(), 0, (int)valuesMs.Position);
-            ReturnStream(valuesMs);
-        }
-
-        private void WriteArray(VariantValue variant, VariantMetadataBuilder 
metadataBuilder, int[] idRemap, MemoryStream ms)
-        {
-            IReadOnlyList<VariantValue> elements = variant.AsArray();
-            int elementCount = elements.Count;
-
-            // Encode all elements into a single stream, recording offsets.
-            MemoryStream valuesMs = RentStream();
-            int offsetCount = elementCount + 1;
-            Span<int> offsets = offsetCount <= StackAllocThreshold
-                ? stackalloc int[offsetCount]
-                : new int[offsetCount];
-            offsets[0] = 0;
-            for (int i = 0; i < elementCount; i++)
-            {
-                WriteValue(elements[i], metadataBuilder, idRemap, valuesMs);
-                offsets[i + 1] = (int)valuesMs.Position;
-            }
-
-            int offsetSize = 
VariantEncodingHelper.ByteWidthForValue(Math.Max(1, offsets[elementCount]));
-            bool isLarge = elementCount > 255;
-
-            // Write header.
-            ms.WriteByte(VariantEncodingHelper.MakeArrayHeader(offsetSize, 
isLarge));
-
-            // Write element count.
-            if (isLarge)
-            {
-                WriteInt32(ms, elementCount);
-            }
-            else
-            {
-                ms.WriteByte((byte)elementCount);
-            }
-
-            // Write offsets.
-            for (int i = 0; i <= elementCount; i++)
-            {
-                WriteSmallInt(ms, offsets[i], offsetSize);
-            }
-
-            // Write element values from the shared buffer.
-            ms.Write(valuesMs.GetBuffer(), 0, (int)valuesMs.Position);
-            ReturnStream(valuesMs);
-        }
-
-        // ---------------------------------------------------------------
-        // Primitive write helpers
-        // ---------------------------------------------------------------
-
-        private void EnsureBuffer()
-        {
-#if !NET8_0_OR_GREATER
-            if (_buffer == null) _buffer = new byte[16];
-#endif
-        }
-
-        private MemoryStream RentStream()
-        {
-            if (_streamPool.Count > 0)
-            {
-                MemoryStream ms = _streamPool.Pop();
-                ms.SetLength(0);
-                return ms;
-            }
-            return new MemoryStream();
-        }
-
-        private void ReturnStream(MemoryStream ms)
-        {
-            _streamPool.Push(ms);
-        }
-
-        private void WriteSmallInt(MemoryStream ms, int value, int byteWidth)
-        {
-#if NET8_0_OR_GREATER
-            Span<byte> buf = stackalloc byte[4];
-            VariantEncodingHelper.WriteLittleEndianInt(buf, value, byteWidth);
-            ms.Write(buf.Slice(0, byteWidth));
-#else
-            VariantEncodingHelper.WriteLittleEndianInt(_buffer, value, 
byteWidth);
-            ms.Write(_buffer, 0, byteWidth);
-#endif
-        }
-
-        private void WriteInt16(MemoryStream ms, short value)
-        {
-#if NET8_0_OR_GREATER
-            Span<byte> buf = stackalloc byte[2];
-            BinaryPrimitives.WriteInt16LittleEndian(buf, value);
-            ms.Write(buf);
-#else
-            BinaryPrimitives.WriteInt16LittleEndian(_buffer, value);
-            ms.Write(_buffer, 0, 2);
-#endif
-        }
-
-        private void WriteInt32(MemoryStream ms, int value)
-        {
-#if NET8_0_OR_GREATER
-            Span<byte> buf = stackalloc byte[4];
-            BinaryPrimitives.WriteInt32LittleEndian(buf, value);
-            ms.Write(buf);
-#else
-            BinaryPrimitives.WriteInt32LittleEndian(_buffer, value);
-            ms.Write(_buffer, 0, 4);
-#endif
-        }
-
-        private void WriteInt64(MemoryStream ms, long value)
-        {
-#if NET8_0_OR_GREATER
-            Span<byte> buf = stackalloc byte[8];
-            BinaryPrimitives.WriteInt64LittleEndian(buf, value);
-            ms.Write(buf);
-#else
-            BinaryPrimitives.WriteInt64LittleEndian(_buffer, value);
-            ms.Write(_buffer, 0, 8);
-#endif
-        }
-
-        private void WriteFloat(MemoryStream ms, float value)
-        {
-#if NET8_0_OR_GREATER
-            Span<byte> buf = stackalloc byte[4];
-            BinaryPrimitives.WriteSingleLittleEndian(buf, value);
-            ms.Write(buf);
-#else
-            int bits = System.Runtime.CompilerServices.Unsafe.As<float, 
int>(ref value);
-            BinaryPrimitives.WriteInt32LittleEndian(_buffer, bits);
-            ms.Write(_buffer, 0, 4);
-#endif
-        }
-
-        private void WriteDouble(MemoryStream ms, double value)
-        {
-#if NET8_0_OR_GREATER
-            Span<byte> buf = stackalloc byte[8];
-            BinaryPrimitives.WriteDoubleLittleEndian(buf, value);
-            ms.Write(buf);
-#else
-            long bits = BitConverter.DoubleToInt64Bits(value);
-            BinaryPrimitives.WriteInt64LittleEndian(_buffer, bits);
-            ms.Write(_buffer, 0, 8);
-#endif
-        }
-
-        // ---------------------------------------------------------------
-        // Streaming JSON encoding
-        // ---------------------------------------------------------------
-
-        /// <summary>
-        /// Encodes UTF-8 JSON bytes directly to the variant binary format
-        /// without creating intermediate <see cref="VariantValue"/> objects.
-        /// </summary>
-        /// <param name="utf8Json">The UTF-8 encoded JSON bytes.</param>
-        /// <returns>A tuple of (metadata bytes, value bytes).</returns>
-        public (byte[] Metadata, byte[] Value) 
EncodeFromJson(ReadOnlySpan<byte> utf8Json)
-        {
-            EnsureBuffer();
-
-            // Pass 1: collect all field names.
-            VariantMetadataBuilder metadataBuilder = new 
VariantMetadataBuilder();
-            Utf8JsonReader reader1 = new Utf8JsonReader(utf8Json);
-            reader1.Read();
-            CollectFieldNamesFromJson(ref reader1, metadataBuilder);
-
-            // Build sorted metadata + get the ID remap.
-            byte[] metadata = metadataBuilder.Build(out int[] idRemap);
-
-            // Pass 2: encode the value.
-            Utf8JsonReader reader2 = new Utf8JsonReader(utf8Json);
-            reader2.Read();
-            using (MemoryStream ms = new MemoryStream())
-            {
-                WriteJsonValue(ref reader2, metadataBuilder, idRemap, ms);
-                return (metadata, ms.ToArray());
-            }
-        }
-
-        private static void CollectFieldNamesFromJson(ref Utf8JsonReader 
reader, VariantMetadataBuilder builder)
-        {
-            switch (reader.TokenType)
-            {
-                case JsonTokenType.StartObject:
-                    while (reader.Read())
-                    {
-                        if (reader.TokenType == JsonTokenType.EndObject)
-                            return;
-                        // PropertyName token
-                        builder.Add(reader.GetString());
-                        reader.Read();
-                        CollectFieldNamesFromJson(ref reader, builder);
-                    }
-                    throw new JsonException("Unterminated JSON object.");
-
-                case JsonTokenType.StartArray:
-                    while (reader.Read())
-                    {
-                        if (reader.TokenType == JsonTokenType.EndArray)
-                            return;
-                        CollectFieldNamesFromJson(ref reader, builder);
-                    }
-                    throw new JsonException("Unterminated JSON array.");
-
-                default:
-                    // Primitive value (null, bool, number, string) — no field 
names.
-                    return;
-            }
-        }
-
-        private void WriteJsonValue(ref Utf8JsonReader reader, 
VariantMetadataBuilder metadataBuilder, int[] idRemap, MemoryStream ms)
-        {
-            switch (reader.TokenType)
-            {
-                case JsonTokenType.Null:
-                    
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.NullType));
-                    return;
-
-                case JsonTokenType.True:
-                    
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.BooleanTrue));
-                    return;
-
-                case JsonTokenType.False:
-                    
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.BooleanFalse));
-                    return;
-
-                case JsonTokenType.Number:
-                    WriteJsonNumber(ref reader, ms);
-                    return;
-
-                case JsonTokenType.String:
-                    WriteString(ms, reader.GetString());
-                    return;
-
-                case JsonTokenType.StartObject:
-                    WriteJsonObject(ref reader, metadataBuilder, idRemap, ms);
-                    return;
-
-                case JsonTokenType.StartArray:
-                    WriteJsonArray(ref reader, metadataBuilder, idRemap, ms);
-                    return;
-
-                default:
-                    throw new JsonException($"Unexpected JSON token type 
{reader.TokenType}.");
-            }
-        }
-
-        private void WriteJsonNumber(ref Utf8JsonReader reader, MemoryStream 
ms)
-        {
-            if (reader.TryGetInt64(out long longValue))
-            {
-                if (longValue >= sbyte.MinValue && longValue <= sbyte.MaxValue)
-                {
-                    
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int8));
-                    ms.WriteByte((byte)(sbyte)longValue);
-                }
-                else if (longValue >= short.MinValue && longValue <= 
short.MaxValue)
-                {
-                    
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int16));
-                    WriteInt16(ms, (short)longValue);
-                }
-                else if (longValue >= int.MinValue && longValue <= 
int.MaxValue)
-                {
-                    
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int32));
-                    WriteInt32(ms, (int)longValue);
-                }
-                else
-                {
-                    
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int64));
-                    WriteInt64(ms, longValue);
-                }
-            }
-            else
-            {
-                
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Double));
-                WriteDouble(ms, reader.GetDouble());
-            }
-        }
-
-        private void WriteJsonObject(ref Utf8JsonReader reader, 
VariantMetadataBuilder metadataBuilder, int[] idRemap, MemoryStream ms)
-        {
-            // Encode all field values into a shared stream, tracking 
positions.
-            MemoryStream valuesMs = RentStream();
-            List<int> fieldIds = new List<int>(16);
-            List<int> valueStarts = new List<int>(16);
-
-            while (reader.Read())
-            {
-                if (reader.TokenType == JsonTokenType.EndObject)
-                    break;
-
-                string name = reader.GetString();
-                fieldIds.Add(idRemap[metadataBuilder.GetId(name)]);
-
-                reader.Read();
-                valueStarts.Add((int)valuesMs.Position);
-                WriteJsonValue(ref reader, metadataBuilder, idRemap, valuesMs);
-            }
-
-            int fieldCount = fieldIds.Count;
-
-            // Sentinel marks end of last value.
-            valueStarts.Add((int)valuesMs.Position);
-
-            // Build sort indices so we can write fields in sorted ID order.
-#if NET8_0_OR_GREATER
-            Span<int> sortOrder = fieldCount <= StackAllocThreshold
-                ? stackalloc int[fieldCount]
-                : new int[fieldCount];
-            for (int i = 0; i < fieldCount; i++)
-            {
-                sortOrder[i] = i;
-            }
-            sortOrder.Sort((a, b) => fieldIds[a].CompareTo(fieldIds[b]));
-#else
-            int[] sortOrder = new int[fieldCount];
-            for (int i = 0; i < fieldCount; i++)
-            {
-                sortOrder[i] = i;
-            }
-            Array.Sort(sortOrder, (a, b) => 
fieldIds[a].CompareTo(fieldIds[b]));
-#endif
-
-            // Build offsets in sorted order.
-            int offsetCount = fieldCount + 1;
-            Span<int> offsets = offsetCount <= StackAllocThreshold
-                ? stackalloc int[offsetCount]
-                : new int[offsetCount];
-            offsets[0] = 0;
-            for (int i = 0; i < fieldCount; i++)
-            {
-                int idx = sortOrder[i];
-                offsets[i + 1] = offsets[i] + (valueStarts[idx + 1] - 
valueStarts[idx]);
-            }
-
-            // Determine sizes.
-            int maxFieldId = fieldCount > 0 ? fieldIds[sortOrder[fieldCount - 
1]] : 0;
-            int fieldIdSize = fieldCount > 0 ? 
VariantEncodingHelper.ByteWidthForValue(maxFieldId) : 1;
-            int offsetSize = 
VariantEncodingHelper.ByteWidthForValue(Math.Max(1, offsets[fieldCount]));
-            bool isLarge = fieldCount > 255;
-
-            // Write header.
-            ms.WriteByte(VariantEncodingHelper.MakeObjectHeader(fieldIdSize, 
offsetSize, isLarge));
-
-            // Write field count.
-            if (isLarge)
-            {
-                WriteInt32(ms, fieldCount);
-            }
-            else
-            {
-                ms.WriteByte((byte)fieldCount);
-            }
-
-            // Write field IDs in sorted order.
-            for (int i = 0; i < fieldCount; i++)
-            {
-                WriteSmallInt(ms, fieldIds[sortOrder[i]], fieldIdSize);
-            }
-
-            // Write offsets.
-            for (int i = 0; i <= fieldCount; i++)
-            {
-                WriteSmallInt(ms, offsets[i], offsetSize);
-            }
-
-            // Write field values in sorted order.
-            byte[] valueBuffer = valuesMs.GetBuffer();
-            for (int i = 0; i < fieldCount; i++)
-            {
-                int idx = sortOrder[i];
-                int start = valueStarts[idx];
-                int length = valueStarts[idx + 1] - start;
-                ms.Write(valueBuffer, start, length);
-            }
-            ReturnStream(valuesMs);
-        }
-
-        private void WriteJsonArray(ref Utf8JsonReader reader, 
VariantMetadataBuilder metadataBuilder, int[] idRemap, MemoryStream ms)
-        {
-            MemoryStream valuesMs = RentStream();
-            List<int> offsets = new List<int>(16);
-            offsets.Add(0);
-
-            while (reader.Read())
-            {
-                if (reader.TokenType == JsonTokenType.EndArray)
-                    break;
-
-                WriteJsonValue(ref reader, metadataBuilder, idRemap, valuesMs);
-                offsets.Add((int)valuesMs.Position);
-            }
-
-            int elementCount = offsets.Count - 1;
-            int offsetSize = 
VariantEncodingHelper.ByteWidthForValue(Math.Max(1, offsets[elementCount]));
-            bool isLarge = elementCount > 255;
-
-            // Write header.
-            ms.WriteByte(VariantEncodingHelper.MakeArrayHeader(offsetSize, 
isLarge));
-
-            // Write element count.
-            if (isLarge)
-            {
-                WriteInt32(ms, elementCount);
-            }
-            else
-            {
-                ms.WriteByte((byte)elementCount);
-            }
-
-            // Write offsets.
-            for (int i = 0; i <= elementCount; i++)
-            {
-                WriteSmallInt(ms, offsets[i], offsetSize);
-            }
-
-            // Write element values.
-            ms.Write(valuesMs.GetBuffer(), 0, (int)valuesMs.Position);
-            ReturnStream(valuesMs);
-        }
     }
 }
diff --git a/src/Apache.Arrow.Variant/VariantValueWriter.cs 
b/src/Apache.Arrow.Variant/VariantValueWriter.cs
new file mode 100644
index 0000000..f5b9498
--- /dev/null
+++ b/src/Apache.Arrow.Variant/VariantValueWriter.cs
@@ -0,0 +1,679 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
+using System.Buffers;
+using System.Buffers.Binary;
+using System.Collections.Generic;
+using System.Data.SqlTypes;
+using System.IO;
+using System.Text;
+
+namespace Apache.Arrow.Variant
+{
+    /// <summary>
+    /// Streams variant value bytes directly from primitive calls, without
+    /// requiring an intermediate <see cref="VariantValue"/>. Use this to
+    /// implement encoders from arbitrary input formats (JSON, CBOR, etc.).
+    /// </summary>
+    /// <remarks>
+    /// Usage pattern:
+    /// <list type="number">
+    /// <item>Create a <see cref="VariantMetadataBuilder"/> and <see 
cref="VariantMetadataBuilder.Add(string)"/> every field name that will 
appear.</item>
+    /// <item>Call <see cref="VariantMetadataBuilder.Build(out int[])"/> to 
produce the metadata bytes and the ID remap.</item>
+    /// <item>Create a <see cref="VariantValueWriter"/> with the metadata 
builder and remap, emit the value via the <c>Write*</c> / <c>Begin*</c> / 
<c>End*</c> methods, then call <see cref="ToArray"/>.</item>
+    /// </list>
+    /// </remarks>
+    public sealed class VariantValueWriter
+    {
+        private const int StackAllocThreshold = 256;
+
+        private readonly VariantMetadataBuilder _metadata;
+        private readonly int[] _idRemap;
+        private readonly MemoryStream _root = new MemoryStream();
+        private readonly Stack<Frame> _frameStack = new Stack<Frame>();
+        private readonly Stack<MemoryStream> _streamPool = new 
Stack<MemoryStream>();
+        private Frame _frame;
+
+#if !NET8_0_OR_GREATER
+        private readonly byte[] _scratch = new byte[16];
+#endif
+
+        /// <summary>
+        /// Creates a writer that produces value bytes referencing the given 
metadata.
+        /// </summary>
+        /// <param name="metadata">The metadata builder used to resolve field 
names to IDs.</param>
+        /// <param name="idRemap">The remap returned by <see 
cref="VariantMetadataBuilder.Build(out int[])"/>.</param>
+        public VariantValueWriter(VariantMetadataBuilder metadata, int[] 
idRemap)
+        {
+            _metadata = metadata ?? throw new 
ArgumentNullException(nameof(metadata));
+            _idRemap = idRemap ?? throw new 
ArgumentNullException(nameof(idRemap));
+
+            if (_metadata.Count != _idRemap.Length)
+            {
+                throw new ArgumentException(
+                    "the idRemap array length must match the metadata builder 
count used to create it.",
+                    nameof(idRemap));
+            }
+        }
+
+        /// <summary>
+        /// Returns the encoded value bytes. All opened objects and arrays 
must be closed.
+        /// </summary>
+        public byte[] ToArray()
+        {
+            if (_frame != null)
+            {
+                throw new InvalidOperationException("Unclosed object or array 
at the top of the writer.");
+            }
+            return _root.ToArray();
+        }
+
+        // ---------------------------------------------------------------
+        // Object / array scope
+        // ---------------------------------------------------------------
+
+        /// <summary>Begins writing an object. Pair with <see 
cref="EndObject"/>.</summary>
+        public void BeginObject()
+        {
+            BeforeWriteValue();
+            _frameStack.Push(_frame);
+            ObjectFrame frame = new ObjectFrame { Buffer = RentStream() };
+            frame.FieldIds = ArrayPool<int>.Shared.Rent(InitialFrameCapacity);
+            frame.ValueStarts = 
ArrayPool<int>.Shared.Rent(InitialFrameCapacity);
+            _frame = frame;
+        }
+
+        /// <summary>
+        /// Writes the name of the next field in the current object. Must be 
called
+        /// before every field value (a primitive, nested object, or nested 
array).
+        /// The name must already exist in the <see 
cref="VariantMetadataBuilder"/>.
+        /// </summary>
+        public void WriteFieldName(string name)
+        {
+            if (!(_frame is ObjectFrame objFrame))
+            {
+                throw new InvalidOperationException("WriteFieldName may only 
be called inside an object scope.");
+            }
+            if (objFrame.PendingValue)
+            {
+                throw new InvalidOperationException("A value must be written 
for the previous field before writing the next field name.");
+            }
+            int fieldId = _idRemap[_metadata.GetId(name)];
+            AppendInt(ref objFrame.FieldIds, ref objFrame.FieldIdCount, 
fieldId);
+            objFrame.PendingValue = true;
+        }
+
+        /// <summary>Ends the current object scope.</summary>
+        public void EndObject()
+        {
+            if (!(_frame is ObjectFrame objFrame))
+            {
+                throw new InvalidOperationException("EndObject called without 
matching BeginObject.");
+            }
+            if (objFrame.PendingValue)
+            {
+                throw new InvalidOperationException("Missing value for the 
last field name before EndObject.");
+            }
+
+            _frame = _frameStack.Pop();
+            MemoryStream output = _frame != null ? _frame.Buffer : _root;
+            WriteObjectBody(output, objFrame);
+            ArrayPool<int>.Shared.Return(objFrame.FieldIds);
+            ArrayPool<int>.Shared.Return(objFrame.ValueStarts);
+            ReturnStream(objFrame.Buffer);
+        }
+
+        /// <summary>Begins writing an array. Pair with <see 
cref="EndArray"/>.</summary>
+        public void BeginArray()
+        {
+            BeforeWriteValue();
+            _frameStack.Push(_frame);
+            ArrayFrame frame = new ArrayFrame { Buffer = RentStream() };
+            frame.ValueStarts = 
ArrayPool<int>.Shared.Rent(InitialFrameCapacity);
+            _frame = frame;
+        }
+
+        /// <summary>Ends the current array scope.</summary>
+        public void EndArray()
+        {
+            if (!(_frame is ArrayFrame arrFrame))
+            {
+                throw new InvalidOperationException("EndArray called without 
matching BeginArray.");
+            }
+
+            _frame = _frameStack.Pop();
+            MemoryStream output = _frame != null ? _frame.Buffer : _root;
+            WriteArrayBody(output, arrFrame);
+            ArrayPool<int>.Shared.Return(arrFrame.ValueStarts);
+            ReturnStream(arrFrame.Buffer);
+        }
+
+        // ---------------------------------------------------------------
+        // Primitive writes
+        // ---------------------------------------------------------------
+
+        /// <summary>Writes a null value.</summary>
+        public void WriteNull()
+        {
+            MemoryStream ms = BeforeWriteValue();
+            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.NullType));
+        }
+
+        /// <summary>Writes a boolean value.</summary>
+        public void WriteBoolean(bool value)
+        {
+            MemoryStream ms = BeforeWriteValue();
+            ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(
+                value ? VariantPrimitiveType.BooleanTrue : 
VariantPrimitiveType.BooleanFalse));
+        }
+
+        /// <summary>Writes an 8-bit signed integer.</summary>
+        public void WriteInt8(sbyte value)
+        {
+            MemoryStream ms = BeforeWriteValue();
+            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int8));
+            ms.WriteByte((byte)value);
+        }
+
+        /// <summary>Writes a 16-bit signed integer.</summary>
+        public void WriteInt16(short value)
+        {
+            MemoryStream ms = BeforeWriteValue();
+            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int16));
+            WriteInt16LE(ms, value);
+        }
+
+        /// <summary>Writes a 32-bit signed integer.</summary>
+        public void WriteInt32(int value)
+        {
+            MemoryStream ms = BeforeWriteValue();
+            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int32));
+            WriteInt32LE(ms, value);
+        }
+
+        /// <summary>Writes a 64-bit signed integer.</summary>
+        public void WriteInt64(long value)
+        {
+            MemoryStream ms = BeforeWriteValue();
+            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int64));
+            WriteInt64LE(ms, value);
+        }
+
+        /// <summary>
+        /// Writes an integer using the narrowest of Int8/Int16/Int32/Int64 
that fits.
+        /// Useful for size-minimising encoders such as JSON.
+        /// </summary>
+        public void WriteIntegerCompact(long value)
+        {
+            if (value >= sbyte.MinValue && value <= sbyte.MaxValue)
+            {
+                WriteInt8((sbyte)value);
+            }
+            else if (value >= short.MinValue && value <= short.MaxValue)
+            {
+                WriteInt16((short)value);
+            }
+            else if (value >= int.MinValue && value <= int.MaxValue)
+            {
+                WriteInt32((int)value);
+            }
+            else
+            {
+                WriteInt64(value);
+            }
+        }
+
+        /// <summary>Writes a 32-bit IEEE 754 float.</summary>
+        public void WriteFloat(float value)
+        {
+            MemoryStream ms = BeforeWriteValue();
+            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Float));
+            WriteFloatLE(ms, value);
+        }
+
+        /// <summary>Writes a 64-bit IEEE 754 double.</summary>
+        public void WriteDouble(double value)
+        {
+            MemoryStream ms = BeforeWriteValue();
+            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Double));
+            WriteDoubleLE(ms, value);
+        }
+
+        /// <summary>Writes a Decimal4 (precision ≤ 9) value.</summary>
+        public void WriteDecimal4(decimal value)
+        {
+            MemoryStream ms = BeforeWriteValue();
+            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Decimal4));
+#if NET8_0_OR_GREATER
+            Span<int> bits = stackalloc int[4];
+            decimal.GetBits(value, bits);
+#else
+            int[] bits = decimal.GetBits(value);
+#endif
+            byte scale = (byte)((bits[3] >> 16) & 0x7F);
+            bool negative = (bits[3] & unchecked((int)0x80000000)) != 0;
+            int unscaled = bits[0];
+            if (negative) unscaled = -unscaled;
+            ms.WriteByte(scale);
+            WriteInt32LE(ms, unscaled);
+        }
+
+        /// <summary>Writes a Decimal8 (precision ≤ 18) value.</summary>
+        public void WriteDecimal8(decimal value)
+        {
+            MemoryStream ms = BeforeWriteValue();
+            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Decimal8));
+#if NET8_0_OR_GREATER
+            Span<int> bits = stackalloc int[4];
+            decimal.GetBits(value, bits);
+#else
+            int[] bits = decimal.GetBits(value);
+#endif
+            byte scale = (byte)((bits[3] >> 16) & 0x7F);
+            bool negative = (bits[3] & unchecked((int)0x80000000)) != 0;
+            long unscaled = ((long)bits[1] << 32) | (uint)bits[0];
+            if (negative) unscaled = -unscaled;
+            ms.WriteByte(scale);
+            WriteInt64LE(ms, unscaled);
+        }
+
+        /// <summary>Writes a Decimal16 (precision ≤ 38) value stored as <see 
cref="SqlDecimal"/>.</summary>
+        public void WriteDecimal16(SqlDecimal value)
+        {
+            MemoryStream ms = BeforeWriteValue();
+            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Decimal16));
+
+            bool positive = value.IsPositive;
+            byte scale = (byte)value.Scale;
+            int[] data = value.Data;
+
+            // SqlDecimal.Data: [0]=least-significant, [3]=most-significant
+            long lo = ((long)(uint)data[1] << 32) | (uint)data[0];
+            long hi = ((long)(uint)data[3] << 32) | (uint)data[2];
+
+            if (!positive)
+            {
+                // Two's complement negate 128-bit
+                lo = ~lo;
+                hi = ~hi;
+                ulong uLo = (ulong)lo + 1;
+                if (uLo == 0) hi++;
+                lo = (long)uLo;
+            }
+
+            ms.WriteByte(scale);
+            WriteInt64LE(ms, lo);
+            WriteInt64LE(ms, hi);
+        }
+
+        /// <summary>Writes a string. Uses the short-string encoding when the 
UTF-8 byte length is ≤ 63.</summary>
+        public void WriteString(string value)
+        {
+            if (value == null) throw new ArgumentNullException(nameof(value));
+            MemoryStream ms = BeforeWriteValue();
+            int byteCount = Encoding.UTF8.GetByteCount(value);
+            if (byteCount <= 63)
+            {
+                
ms.WriteByte(VariantEncodingHelper.MakeShortStringHeader(byteCount));
+            }
+            else
+            {
+                
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.String));
+                WriteInt32LE(ms, byteCount);
+            }
+
+            // Encode UTF-8 directly into the MemoryStream's buffer.
+            int dataPos = (int)ms.Position;
+            int needed = dataPos + byteCount;
+            if (needed > ms.Length)
+            {
+                ms.SetLength(needed);
+            }
+            Encoding.UTF8.GetBytes(value, 0, value.Length, ms.GetBuffer(), 
dataPos);
+            ms.Position = needed;
+        }
+
+        /// <summary>Writes a binary blob.</summary>
+        public void WriteBinary(byte[] data)
+        {
+            if (data == null) throw new ArgumentNullException(nameof(data));
+            MemoryStream ms = BeforeWriteValue();
+            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Binary));
+            WriteInt32LE(ms, data.Length);
+            ms.Write(data, 0, data.Length);
+        }
+
+        /// <summary>Writes a UUID.</summary>
+        public void WriteUuid(Guid value)
+        {
+            MemoryStream ms = BeforeWriteValue();
+            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Uuid));
+#if NET8_0_OR_GREATER
+            Span<byte> buf = stackalloc byte[16];
+            value.TryWriteBytes(buf, bigEndian: true, out int _);
+            ms.Write(buf);
+#else
+            byte[] native = value.ToByteArray();
+            // Convert from .NET mixed-endian to big-endian (RFC 4122).
+            _scratch[0] = native[3]; _scratch[1] = native[2]; _scratch[2] = 
native[1]; _scratch[3] = native[0];
+            _scratch[4] = native[5]; _scratch[5] = native[4];
+            _scratch[6] = native[7]; _scratch[7] = native[6];
+            Buffer.BlockCopy(native, 8, _scratch, 8, 8);
+            ms.Write(_scratch, 0, 16);
+#endif
+        }
+
+        /// <summary>Writes a date as days since the Unix epoch.</summary>
+        public void WriteDateDays(int days)
+        {
+            MemoryStream ms = BeforeWriteValue();
+            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Date));
+            WriteInt32LE(ms, days);
+        }
+
+        /// <summary>Writes a timestamp (tz-adjusted microseconds since the 
Unix epoch).</summary>
+        public void WriteTimestampMicros(long micros)
+        {
+            MemoryStream ms = BeforeWriteValue();
+            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Timestamp));
+            WriteInt64LE(ms, micros);
+        }
+
+        /// <summary>Writes a timestamp-without-timezone (microseconds since 
the Unix epoch).</summary>
+        public void WriteTimestampNtzMicros(long micros)
+        {
+            MemoryStream ms = BeforeWriteValue();
+            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.TimestampNtz));
+            WriteInt64LE(ms, micros);
+        }
+
+        /// <summary>Writes a time-without-timezone value (microseconds since 
midnight).</summary>
+        public void WriteTimeNtzMicros(long micros)
+        {
+            MemoryStream ms = BeforeWriteValue();
+            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.TimeNtz));
+            WriteInt64LE(ms, micros);
+        }
+
+        /// <summary>Writes a timestamp with timezone (nanoseconds since the 
Unix epoch).</summary>
+        public void WriteTimestampTzNanos(long nanos)
+        {
+            MemoryStream ms = BeforeWriteValue();
+            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.TimestampTzNanos));
+            WriteInt64LE(ms, nanos);
+        }
+
+        /// <summary>Writes a timestamp without timezone (nanoseconds since 
the Unix epoch).</summary>
+        public void WriteTimestampNtzNanos(long nanos)
+        {
+            MemoryStream ms = BeforeWriteValue();
+            
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.TimestampNtzNanos));
+            WriteInt64LE(ms, nanos);
+        }
+
+        // ---------------------------------------------------------------
+        // Internal bookkeeping
+        // ---------------------------------------------------------------
+
+        private MemoryStream BeforeWriteValue()
+        {
+            if (_frame is ObjectFrame objFrame)
+            {
+                if (!objFrame.PendingValue)
+                {
+                    throw new InvalidOperationException("A field name is 
required before writing an object field value. Call WriteFieldName first.");
+                }
+                AppendInt(ref objFrame.ValueStarts, ref 
objFrame.ValueStartCount, (int)objFrame.Buffer.Position);
+                objFrame.PendingValue = false;
+                return objFrame.Buffer;
+            }
+            if (_frame is ArrayFrame arrFrame)
+            {
+                AppendInt(ref arrFrame.ValueStarts, ref 
arrFrame.ValueStartCount, (int)arrFrame.Buffer.Position);
+                return arrFrame.Buffer;
+            }
+            return _root;
+        }
+
+        private static void AppendInt(ref int[] array, ref int count, int 
value)
+        {
+            if (count == array.Length)
+            {
+                int[] grown = ArrayPool<int>.Shared.Rent(array.Length * 2);
+                Array.Copy(array, 0, grown, 0, count);
+                ArrayPool<int>.Shared.Return(array);
+                array = grown;
+            }
+            array[count++] = value;
+        }
+
+        private void WriteObjectBody(MemoryStream output, ObjectFrame frame)
+        {
+            int fieldCount = frame.FieldIdCount;
+            // Sentinel marks the end of the last value in the frame buffer.
+            AppendInt(ref frame.ValueStarts, ref frame.ValueStartCount, 
(int)frame.Buffer.Position);
+
+            int[] fieldIds = frame.FieldIds;
+            int[] valueStarts = frame.ValueStarts;
+
+            // Sort indices so fields are emitted in sorted-field-id order.
+#if NET8_0_OR_GREATER
+            Span<int> sortOrder = fieldCount <= StackAllocThreshold
+                ? stackalloc int[fieldCount]
+                : new int[fieldCount];
+            for (int i = 0; i < fieldCount; i++)
+            {
+                sortOrder[i] = i;
+            }
+            int[] fieldIdsLocal = fieldIds;
+            sortOrder.Sort((a, b) => 
fieldIdsLocal[a].CompareTo(fieldIdsLocal[b]));
+#else
+            int[] sortOrder = new int[fieldCount];
+            for (int i = 0; i < fieldCount; i++)
+            {
+                sortOrder[i] = i;
+            }
+            int[] fieldIdsLocal = fieldIds;
+            Array.Sort(sortOrder, (a, b) => 
fieldIdsLocal[a].CompareTo(fieldIdsLocal[b]));
+#endif
+
+            int offsetCount = fieldCount + 1;
+            Span<int> offsets = offsetCount <= StackAllocThreshold
+                ? stackalloc int[offsetCount]
+                : new int[offsetCount];
+            offsets[0] = 0;
+            for (int i = 0; i < fieldCount; i++)
+            {
+                int idx = sortOrder[i];
+                offsets[i + 1] = offsets[i] + (valueStarts[idx + 1] - 
valueStarts[idx]);
+            }
+
+            int maxFieldId = fieldCount > 0 ? fieldIds[sortOrder[fieldCount - 
1]] : 0;
+            int fieldIdSize = fieldCount > 0 ? 
VariantEncodingHelper.ByteWidthForValue(maxFieldId) : 1;
+            int offsetSize = 
VariantEncodingHelper.ByteWidthForValue(Math.Max(1, offsets[fieldCount]));
+            bool isLarge = fieldCount > 255;
+
+            
output.WriteByte(VariantEncodingHelper.MakeObjectHeader(fieldIdSize, 
offsetSize, isLarge));
+
+            if (isLarge)
+            {
+                WriteInt32LE(output, fieldCount);
+            }
+            else
+            {
+                output.WriteByte((byte)fieldCount);
+            }
+
+            for (int i = 0; i < fieldCount; i++)
+            {
+                WriteSmallInt(output, fieldIds[sortOrder[i]], fieldIdSize);
+            }
+
+            for (int i = 0; i <= fieldCount; i++)
+            {
+                WriteSmallInt(output, offsets[i], offsetSize);
+            }
+
+            byte[] valueBuffer = frame.Buffer.GetBuffer();
+            for (int i = 0; i < fieldCount; i++)
+            {
+                int idx = sortOrder[i];
+                int start = valueStarts[idx];
+                int length = valueStarts[idx + 1] - start;
+                output.Write(valueBuffer, start, length);
+            }
+        }
+
+        private void WriteArrayBody(MemoryStream output, ArrayFrame frame)
+        {
+            int elementCount = frame.ValueStartCount;
+            // Sentinel marks the end of the last element.
+            AppendInt(ref frame.ValueStarts, ref frame.ValueStartCount, 
(int)frame.Buffer.Position);
+            int[] valueStarts = frame.ValueStarts;
+
+            int offsetSize = 
VariantEncodingHelper.ByteWidthForValue(Math.Max(1, valueStarts[elementCount]));
+            bool isLarge = elementCount > 255;
+
+            output.WriteByte(VariantEncodingHelper.MakeArrayHeader(offsetSize, 
isLarge));
+
+            if (isLarge)
+            {
+                WriteInt32LE(output, elementCount);
+            }
+            else
+            {
+                output.WriteByte((byte)elementCount);
+            }
+
+            for (int i = 0; i <= elementCount; i++)
+            {
+                WriteSmallInt(output, valueStarts[i], offsetSize);
+            }
+
+            output.Write(frame.Buffer.GetBuffer(), 0, 
(int)frame.Buffer.Position);
+        }
+
+        private MemoryStream RentStream()
+        {
+            if (_streamPool.Count > 0)
+            {
+                MemoryStream ms = _streamPool.Pop();
+                ms.SetLength(0);
+                return ms;
+            }
+            return new MemoryStream();
+        }
+
+        private void ReturnStream(MemoryStream ms)
+        {
+            _streamPool.Push(ms);
+        }
+
+        private void WriteSmallInt(MemoryStream ms, int value, int byteWidth)
+        {
+#if NET8_0_OR_GREATER
+            Span<byte> buf = stackalloc byte[4];
+            VariantEncodingHelper.WriteLittleEndianInt(buf, value, byteWidth);
+            ms.Write(buf.Slice(0, byteWidth));
+#else
+            VariantEncodingHelper.WriteLittleEndianInt(_scratch, value, 
byteWidth);
+            ms.Write(_scratch, 0, byteWidth);
+#endif
+        }
+
+        private void WriteInt16LE(MemoryStream ms, short value)
+        {
+#if NET8_0_OR_GREATER
+            Span<byte> buf = stackalloc byte[2];
+            BinaryPrimitives.WriteInt16LittleEndian(buf, value);
+            ms.Write(buf);
+#else
+            BinaryPrimitives.WriteInt16LittleEndian(_scratch, value);
+            ms.Write(_scratch, 0, 2);
+#endif
+        }
+
+        private void WriteInt32LE(MemoryStream ms, int value)
+        {
+#if NET8_0_OR_GREATER
+            Span<byte> buf = stackalloc byte[4];
+            BinaryPrimitives.WriteInt32LittleEndian(buf, value);
+            ms.Write(buf);
+#else
+            BinaryPrimitives.WriteInt32LittleEndian(_scratch, value);
+            ms.Write(_scratch, 0, 4);
+#endif
+        }
+
+        private void WriteInt64LE(MemoryStream ms, long value)
+        {
+#if NET8_0_OR_GREATER
+            Span<byte> buf = stackalloc byte[8];
+            BinaryPrimitives.WriteInt64LittleEndian(buf, value);
+            ms.Write(buf);
+#else
+            BinaryPrimitives.WriteInt64LittleEndian(_scratch, value);
+            ms.Write(_scratch, 0, 8);
+#endif
+        }
+
+        private void WriteFloatLE(MemoryStream ms, float value)
+        {
+#if NET8_0_OR_GREATER
+            Span<byte> buf = stackalloc byte[4];
+            BinaryPrimitives.WriteSingleLittleEndian(buf, value);
+            ms.Write(buf);
+#else
+            int bits = System.Runtime.CompilerServices.Unsafe.As<float, 
int>(ref value);
+            BinaryPrimitives.WriteInt32LittleEndian(_scratch, bits);
+            ms.Write(_scratch, 0, 4);
+#endif
+        }
+
+        private void WriteDoubleLE(MemoryStream ms, double value)
+        {
+#if NET8_0_OR_GREATER
+            Span<byte> buf = stackalloc byte[8];
+            BinaryPrimitives.WriteDoubleLittleEndian(buf, value);
+            ms.Write(buf);
+#else
+            long bits = BitConverter.DoubleToInt64Bits(value);
+            BinaryPrimitives.WriteInt64LittleEndian(_scratch, bits);
+            ms.Write(_scratch, 0, 8);
+#endif
+        }
+
+        private const int InitialFrameCapacity = 16;
+
+        private abstract class Frame
+        {
+            public MemoryStream Buffer;
+            public int[] ValueStarts;
+            public int ValueStartCount;
+        }
+
+        private sealed class ObjectFrame : Frame
+        {
+            public int[] FieldIds;
+            public int FieldIdCount;
+            public bool PendingValue;
+        }
+
+        private sealed class ArrayFrame : Frame
+        {
+        }
+    }
+}
diff --git a/test/Apache.Arrow.Variant.Tests/Apache.Arrow.Variant.Tests.csproj 
b/test/Apache.Arrow.Operations.Tests/Apache.Arrow.Operations.Tests.csproj
similarity index 91%
copy from test/Apache.Arrow.Variant.Tests/Apache.Arrow.Variant.Tests.csproj
copy to test/Apache.Arrow.Operations.Tests/Apache.Arrow.Operations.Tests.csproj
index 1e62a24..e23b7c4 100644
--- a/test/Apache.Arrow.Variant.Tests/Apache.Arrow.Variant.Tests.csproj
+++ b/test/Apache.Arrow.Operations.Tests/Apache.Arrow.Operations.Tests.csproj
@@ -27,7 +27,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <ProjectReference 
Include="..\..\src\Apache.Arrow.Variant\Apache.Arrow.Variant.csproj" />
+    <ProjectReference 
Include="..\..\src\Apache.Arrow.Operations\Apache.Arrow.Operations.csproj" />
   </ItemGroup>
 
 </Project>
diff --git a/test/Apache.Arrow.Operations.Tests/Json/VariantDecimalJsonTests.cs 
b/test/Apache.Arrow.Operations.Tests/Json/VariantDecimalJsonTests.cs
new file mode 100644
index 0000000..c65d120
--- /dev/null
+++ b/test/Apache.Arrow.Operations.Tests/Json/VariantDecimalJsonTests.cs
@@ -0,0 +1,117 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System.Collections.Generic;
+using System.Data.SqlTypes;
+using System.Text.Json;
+using Apache.Arrow.Operations.Json;
+using Apache.Arrow.Variant;
+using Xunit;
+
+namespace Apache.Arrow.Operations.Tests
+{
+    public class VariantDecimalJsonTests
+    {
+        // Minimal metadata: version 1, 0 dictionary entries.
+        private static readonly byte[] MinimalMetadata = new byte[] { 0x01, 
0x00 };
+
+        // Header bytes: (primitiveType << 2) | basicType, basicType=0 for 
Primitive
+        // Decimal4=8 -> 0x20, Decimal8=9 -> 0x24, Decimal16=10 -> 0x28
+        private const byte Decimal16Header = 0x28;
+
+        // ---------------------------------------------------------------
+        // JSON writer: large Decimal16 produces valid JSON number
+        // ---------------------------------------------------------------
+
+        [Fact]
+        public void JsonWriter_LargeDecimal16_WritesValidJson()
+        {
+            byte[] value = new byte[]
+            {
+                Decimal16Header,
+                0x00, // scale = 0
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00
+            };
+
+            string json = VariantJsonWriter.ToJson(MinimalMetadata, value);
+            Assert.Equal("79228162514264337593543950336", json);
+        }
+
+        // ---------------------------------------------------------------
+        // JSON converter: VariantValue with SqlDecimal storage writes valid 
JSON
+        // ---------------------------------------------------------------
+
+        [Fact]
+        public void JsonConverter_SqlDecimalStorage_WritesValidJson()
+        {
+            SqlDecimal sd = 
SqlDecimal.Parse("99999999999999999999999999999999999999");
+            VariantValue vv = VariantValue.FromSqlDecimal(sd);
+
+            string json = VariantJsonWriter.ToJson(vv);
+            Assert.Equal("99999999999999999999999999999999999999", json);
+        }
+
+        // ---------------------------------------------------------------
+        // JSON writer for negative large Decimal16
+        // ---------------------------------------------------------------
+
+        [Fact]
+        public void JsonWriter_NegativeLargeDecimal16_WritesValidJson()
+        {
+            SqlDecimal sd = SqlDecimal.Parse("-79228162514264337593543950336");
+            VariantValue vv = VariantValue.FromSqlDecimal(sd);
+            VariantBuilder builder = new VariantBuilder();
+            (byte[] metadata, byte[] value) = builder.Encode(vv);
+
+            string json = VariantJsonWriter.ToJson(metadata, value);
+            Assert.Equal("-79228162514264337593543950336", json);
+        }
+
+        // ---------------------------------------------------------------
+        // JSON writer for small Decimal16 (decimal path)
+        // ---------------------------------------------------------------
+
+        [Fact]
+        public void JsonWriter_SmallDecimal16_WritesNumberValue()
+        {
+            VariantValue vv = VariantValue.FromDecimal16(42.5m);
+            VariantBuilder builder = new VariantBuilder();
+            (byte[] metadata, byte[] value) = builder.Encode(vv);
+
+            string json = VariantJsonWriter.ToJson(metadata, value);
+            Assert.Equal("42.5", json);
+        }
+
+        // ---------------------------------------------------------------
+        // JSON converter serialization of nested SqlDecimal
+        // ---------------------------------------------------------------
+
+        [Fact]
+        public void JsonConverter_ObjectWithSqlDecimal_WritesValidJson()
+        {
+            SqlDecimal sd = 
SqlDecimal.Parse("99999999999999999999999999999999999999");
+            VariantValue obj = VariantValue.FromObject(new Dictionary<string, 
VariantValue>
+            {
+                { "val", VariantValue.FromSqlDecimal(sd) },
+            });
+
+            JsonSerializerOptions options = new JsonSerializerOptions();
+            options.Converters.Add(VariantJsonConverter.Instance);
+            string json = JsonSerializer.Serialize(obj, options);
+            Assert.Equal("{\"val\":99999999999999999999999999999999999999}", 
json);
+        }
+    }
+}
diff --git a/test/Apache.Arrow.Variant.Tests/VariantJsonTests.cs 
b/test/Apache.Arrow.Operations.Tests/Json/VariantJsonTests.cs
similarity index 99%
rename from test/Apache.Arrow.Variant.Tests/VariantJsonTests.cs
rename to test/Apache.Arrow.Operations.Tests/Json/VariantJsonTests.cs
index b784634..65a26dc 100644
--- a/test/Apache.Arrow.Variant.Tests/VariantJsonTests.cs
+++ b/test/Apache.Arrow.Operations.Tests/Json/VariantJsonTests.cs
@@ -17,10 +17,11 @@ using System;
 using System.Collections.Generic;
 using System.Data.SqlTypes;
 using System.Text.Json;
-using Apache.Arrow.Variant.Json;
+using Apache.Arrow.Operations.Json;
+using Apache.Arrow.Variant;
 using Xunit;
 
-namespace Apache.Arrow.Variant.Tests
+namespace Apache.Arrow.Operations.Tests
 {
     public class VariantJsonTests
     {
diff --git 
a/test/Apache.Arrow.Variant.Benchmarks/Apache.Arrow.Variant.Benchmarks.csproj 
b/test/Apache.Arrow.Variant.Benchmarks/Apache.Arrow.Variant.Benchmarks.csproj
index 1a3757a..1108a2e 100644
--- 
a/test/Apache.Arrow.Variant.Benchmarks/Apache.Arrow.Variant.Benchmarks.csproj
+++ 
b/test/Apache.Arrow.Variant.Benchmarks/Apache.Arrow.Variant.Benchmarks.csproj
@@ -11,6 +11,7 @@
 
   <ItemGroup>
     <ProjectReference 
Include="..\..\src\Apache.Arrow.Variant\Apache.Arrow.Variant.csproj" />
+    <ProjectReference 
Include="..\..\src\Apache.Arrow.Operations\Apache.Arrow.Operations.csproj" />
   </ItemGroup>
 
 </Project>
diff --git a/test/Apache.Arrow.Variant.Benchmarks/EncodingBenchmarks.cs 
b/test/Apache.Arrow.Variant.Benchmarks/EncodingBenchmarks.cs
index 73c7c96..7451355 100644
--- a/test/Apache.Arrow.Variant.Benchmarks/EncodingBenchmarks.cs
+++ b/test/Apache.Arrow.Variant.Benchmarks/EncodingBenchmarks.cs
@@ -17,7 +17,7 @@ using System.Buffers;
 using System.Collections.Generic;
 using System.Text;
 using System.Text.Json;
-using Apache.Arrow.Variant.Json;
+using Apache.Arrow.Operations.Json;
 using BenchmarkDotNet.Attributes;
 
 namespace Apache.Arrow.Variant.Benchmarks
@@ -130,28 +130,25 @@ namespace Apache.Arrow.Variant.Benchmarks
         }
 
         // ---------------------------------------------------------------
-        // EncodeFromJson: UTF-8 JSON → binary (streaming, no VariantValue)
+        // VariantJsonReader: UTF-8 JSON → binary (streaming, no VariantValue)
         // ---------------------------------------------------------------
 
         [Benchmark]
         public byte[] EncodeFromJson_FlatObject()
         {
-            VariantBuilder builder = new VariantBuilder();
-            return builder.EncodeFromJson(_flatJson).Value;
+            return VariantJsonReader.Parse(_flatJson).Value;
         }
 
         [Benchmark]
         public byte[] EncodeFromJson_NestedObject()
         {
-            VariantBuilder builder = new VariantBuilder();
-            return builder.EncodeFromJson(_nestedJson).Value;
+            return VariantJsonReader.Parse(_nestedJson).Value;
         }
 
         [Benchmark]
         public byte[] EncodeFromJson_LargeArray()
         {
-            VariantBuilder builder = new VariantBuilder();
-            return builder.EncodeFromJson(_arrayJson).Value;
+            return VariantJsonReader.Parse(_arrayJson).Value;
         }
 
         // ---------------------------------------------------------------
diff --git a/test/Apache.Arrow.Variant.Tests/Apache.Arrow.Variant.Tests.csproj 
b/test/Apache.Arrow.Variant.Tests/Apache.Arrow.Variant.Tests.csproj
index 1e62a24..4471cfd 100644
--- a/test/Apache.Arrow.Variant.Tests/Apache.Arrow.Variant.Tests.csproj
+++ b/test/Apache.Arrow.Variant.Tests/Apache.Arrow.Variant.Tests.csproj
@@ -24,10 +24,12 @@
     <PackageReference Include="Microsoft.NET.Test.Sdk" />
     <PackageReference Include="xunit" />
     <PackageReference Include="xunit.skippablefact" />
+    <PackageReference Include="System.Text.Json" />
   </ItemGroup>
 
   <ItemGroup>
     <ProjectReference 
Include="..\..\src\Apache.Arrow.Variant\Apache.Arrow.Variant.csproj" />
+    <ProjectReference 
Include="..\..\src\Apache.Arrow.Operations\Apache.Arrow.Operations.csproj" />
   </ItemGroup>
 
 </Project>
diff --git a/test/Apache.Arrow.Variant.Tests/VariantSqlDecimalTests.cs 
b/test/Apache.Arrow.Variant.Tests/VariantSqlDecimalTests.cs
index bf1cad1..ce81ec4 100644
--- a/test/Apache.Arrow.Variant.Tests/VariantSqlDecimalTests.cs
+++ b/test/Apache.Arrow.Variant.Tests/VariantSqlDecimalTests.cs
@@ -16,8 +16,6 @@
 using System;
 using System.Collections.Generic;
 using System.Data.SqlTypes;
-using System.Text.Json;
-using Apache.Arrow.Variant.Json;
 using Xunit;
 
 namespace Apache.Arrow.Variant.Tests
@@ -278,39 +276,6 @@ namespace Apache.Arrow.Variant.Tests
             Assert.Equal(SqlDecimal.Parse("79228162514264337593543950336"), 
result);
         }
 
-        // ---------------------------------------------------------------
-        // JSON writer: large Decimal16 produces valid JSON number
-        // ---------------------------------------------------------------
-
-        [Fact]
-        public void JsonWriter_LargeDecimal16_WritesValidJson()
-        {
-            byte[] value = new byte[]
-            {
-                Decimal16Header,
-                0x00, // scale = 0
-                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-                0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00
-            };
-
-            string json = VariantJsonWriter.ToJson(MinimalMetadata, value);
-            Assert.Equal("79228162514264337593543950336", json);
-        }
-
-        // ---------------------------------------------------------------
-        // JSON converter: VariantValue with SqlDecimal storage writes valid 
JSON
-        // ---------------------------------------------------------------
-
-        [Fact]
-        public void JsonConverter_SqlDecimalStorage_WritesValidJson()
-        {
-            SqlDecimal sd = 
SqlDecimal.Parse("99999999999999999999999999999999999999");
-            VariantValue vv = VariantValue.FromSqlDecimal(sd);
-
-            string json = VariantJsonWriter.ToJson(vv);
-            Assert.Equal("99999999999999999999999999999999999999", json);
-        }
-
         // ---------------------------------------------------------------
         // Round-trip with materialization
         // ---------------------------------------------------------------
@@ -500,37 +465,6 @@ namespace Apache.Arrow.Variant.Tests
             Assert.Equal(79228162514264337593543950335m, vv.AsDecimal());
         }
 
-        // ---------------------------------------------------------------
-        // JSON writer for negative large Decimal16
-        // ---------------------------------------------------------------
-
-        [Fact]
-        public void JsonWriter_NegativeLargeDecimal16_WritesValidJson()
-        {
-            SqlDecimal sd = SqlDecimal.Parse("-79228162514264337593543950336");
-            VariantValue vv = VariantValue.FromSqlDecimal(sd);
-            VariantBuilder builder = new VariantBuilder();
-            (byte[] metadata, byte[] value) = builder.Encode(vv);
-
-            string json = VariantJsonWriter.ToJson(metadata, value);
-            Assert.Equal("-79228162514264337593543950336", json);
-        }
-
-        // ---------------------------------------------------------------
-        // JSON writer for small Decimal16 (decimal path)
-        // ---------------------------------------------------------------
-
-        [Fact]
-        public void JsonWriter_SmallDecimal16_WritesNumberValue()
-        {
-            VariantValue vv = VariantValue.FromDecimal16(42.5m);
-            VariantBuilder builder = new VariantBuilder();
-            (byte[] metadata, byte[] value) = builder.Encode(vv);
-
-            string json = VariantJsonWriter.ToJson(metadata, value);
-            Assert.Equal("42.5", json);
-        }
-
         // ---------------------------------------------------------------
         // GetSqlDecimal on non-decimal primitive throws
         // ---------------------------------------------------------------
@@ -606,25 +540,6 @@ namespace Apache.Arrow.Variant.Tests
             Assert.Equal(1.23m, elements[2].AsDecimal());
         }
 
-        // ---------------------------------------------------------------
-        // JSON converter serialization of nested SqlDecimal
-        // ---------------------------------------------------------------
-
-        [Fact]
-        public void JsonConverter_ObjectWithSqlDecimal_WritesValidJson()
-        {
-            SqlDecimal sd = 
SqlDecimal.Parse("99999999999999999999999999999999999999");
-            VariantValue obj = VariantValue.FromObject(new Dictionary<string, 
VariantValue>
-            {
-                { "val", VariantValue.FromSqlDecimal(sd) },
-            });
-
-            JsonSerializerOptions options = new JsonSerializerOptions();
-            options.Converters.Add(VariantJsonConverter.Instance);
-            string json = JsonSerializer.Serialize(obj, options);
-            Assert.Equal("{\"val\":99999999999999999999999999999999999999}", 
json);
-        }
-
         // ---------------------------------------------------------------
         // Scale preservation round-trip
         // ---------------------------------------------------------------

Reply via email to