プログラム系統備忘録ブログ

記事中のコードは自己責任の下でご自由にどうぞ。

C#でJSONを読み込む方法によっては末尾コンマでも読める話

確認時の.NET Frameworkのバージョン: 4.6.00081

.NET標準のメソッドでJSONを読み込もうとした時、「末尾がコンマで終わる配列」「末尾がコンマで終わるオブジェクト」というJSON textではないものを、方法によっては読める、という記事です(長い)。
("trailing comma"の日本語訳って、何か定着した言い回しはあるのでしょうか。)

JSONの構文

JSONの構文は http://json.org/ のRailroad Diagramが分かりやすいです。
array, objectともに、要素間だけコンマが許されており、末尾コンマは許されていません。

なお RFC4627 の"4. Parsers"、および RFC7159 の"9. Parsers"に次の記述があるので、JSON textではない文字列を受け入れていても、JSONパーサーとして妥当です。

A JSON parser MAY accept non-JSON forms or extensions.

JsonReaderWriterFactory.CreateJsonReaderでは読める

JsonReaderWriterFactory Class は.NET3.5からあるものです。
CreateJsonReader Method で読み込むと、末尾コンマの場合でも正常に読み込めました。
以下確認コード。C#コードの下に、標準出力の内容を記載しています。

末尾コンマ配列版:

using System;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace CsConsoleTest
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            var json = "[1,]";
            var buffer = Encoding.UTF8.GetBytes(json);
            using (var reader = JsonReaderWriterFactory.CreateJsonReader(
                buffer,
                XmlDictionaryReaderQuotas.Max))
            {
                var element = XElement.Load(reader);
                Console.WriteLine(element);
            }
        }
    }
}
<root type="array">
  <item type="number">1</item>
</root>

末尾コンマオブジェクト版:

using System;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace CsConsoleTest
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            var json = @"{""key"":123,}";
            var buffer = Encoding.UTF8.GetBytes(json);
            using (var reader = JsonReaderWriterFactory.CreateJsonReader(
                buffer,
                XmlDictionaryReaderQuotas.Max))
            {
                var element = XElement.Load(reader);
                Console.WriteLine(element);
            }
        }
    }
}
<root type="object">
  <key type="number">123</key>
</root>

JavaScriptSerializer.Deserializeでは例外発生

JavaScriptSerializer Class も同様に.NET3.5からあります。
Deserialize Method で読み込むと、末尾コンマの場合ではArgumentExceptionが送出されました。
以下確認コード。C#コードの下に、例外の詳細を記載しています。

末尾コンマ配列:

using System;
using System.Web.Script.Serialization;

namespace CsConsoleTest
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            var json = "[1,]";
            var serializer = new JavaScriptSerializer();
            var obj = serializer.Deserialize<object>(json); // 例外発生
            Console.WriteLine(obj);
        }
    }
}
ハンドルされていない例外: System.ArgumentException: 無効な配列が渡されました。末尾に余分な ',' があります。 (4): [1,]
   場所 System.Web.Script.Serialization.JavaScriptObjectDeserializer.DeserializeList(Int32 depth)
   場所 System.Web.Script.Serialization.JavaScriptObjectDeserializer.DeserializeInternal(Int32 depth)
   場所 System.Web.Script.Serialization.JavaScriptObjectDeserializer.BasicDeserialize(String input, Int32 depthLimit, JavaScriptSerializer serializer)
   場所 System.Web.Script.Serialization.JavaScriptSerializer.Deserialize(JavaScriptSerializer serializer, String input, Type type, Int32 depthLimit)
   場所 System.Web.Script.Serialization.JavaScriptSerializer.Deserialize[T](String input)
   場所 CsConsoleTest.Program.Main(String[] args)

末尾コンマオブジェクト:

using System;
using System.Web.Script.Serialization;

namespace CsConsoleTest
{
    internal class Program
    {
        private static void Main(string[] args)
        {

            var json = @"{""key"":123,}";
            var serializer = new JavaScriptSerializer();
            var obj = serializer.Deserialize<object>(json); // 例外発生
            Console.WriteLine(obj);
        }
    }
}
ハンドルされていない例外: System.ArgumentException: 無効な JSON プリミティブです: 。
   場所 System.Web.Script.Serialization.JavaScriptObjectDeserializer.DeserializePrimitiveObject()
   場所 System.Web.Script.Serialization.JavaScriptObjectDeserializer.DeserializeInternal(Int32 depth)
   場所 System.Web.Script.Serialization.JavaScriptObjectDeserializer.DeserializeDictionary(Int32 depth)
   場所 System.Web.Script.Serialization.JavaScriptObjectDeserializer.DeserializeInternal(Int32 depth)
   場所 System.Web.Script.Serialization.JavaScriptObjectDeserializer.BasicDeserialize(String input, Int32 depthLimit, JavaScriptSerializer serializer)
   場所 System.Web.Script.Serialization.JavaScriptSerializer.Deserialize(JavaScriptSerializer serializer, String input, Type type, Int32 depthLimit)
   場所 System.Web.Script.Serialization.JavaScriptSerializer.Deserialize[T](String input)
   場所 CsConsoleTest.Program.Main(String[] args)