Edited at

非数をJSONに入れようとするとどうなるか

JSON には非数(NaN)は入れられない。入れられるフォーマットになっていないので仕方ない。

無限大も入れられない。入れられるフォーマットになっていないので仕方ない。

仕方ないんだけど、入れようとしたらどうなってしまうのか、各言語の対応を見ていく。


Ruby

まずはソースコード:


ruby

require "json"

def test(e)
print( e.inspect, ":" )
begin
puts([e].to_json)
rescue=>e
p e
end
end

test( Float::NAN )
test( Float::INFINITY )


これを実行すると:

NaN:#<JSON::GeneratorError: 865: NaN not allowed in JSON>

Infinity:#<JSON::GeneratorError: 862: Infinity not allowed in JSON>

となる。

両者とも例外。まあそうだよね。


Go

まずはソースコード:


go

package main

import (
"encoding/json"
"fmt"
"math"
)

func test(val float64) {
e := []float64{val}
j, err := json.Marshal(e)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(string(j))
}
}

func main() {
test(math.NaN())
test(math.Inf(1))
}


実行するとこうなる:

json: unsupported value: NaN

json: unsupported value: +Inf

go なので例外でも panic でもなく、エラーになる。


JavaScript(node)

まずはソースコード:


javascript(node)

"use strict";

const test = (e)=>{
try{
console.log( ">%s: %s", e, JSON.stringify( [e] ) );
}
catch(err){
console.log( ">%s: %s", e, err);
}
};

test(NaN);
test(Infinity);
// 以下は今回の記事と関係ないけど面白いので:
test(undefined);
test(null);
test({a:undefined});
test({a:null});


実行するとこうなる:

>NaN: [null]

>Infinity: [null]
>undefined: [null]
>null: [null]
>[object Object]: [{}]
>[object Object]: [{"a":null}]

例外にもエラーにもならず、黙って null にされる。

今回の記事と関係ないけど、nullundefined などを JSON.stringify に食べさせると下表のようになる:

入力
返戻値
typeof(返戻値)

undefined
undefined
undefined

null
'null'
string

[undefined]
'[null]'
string

[null]
'[null]'
string

{a:undefined}
'{}'
string

{a:null}
'{"a":null}'
string

JavaScript 難しい。


C#

初出時は


見出しに「C#」と普通に書く方法がわからないのでバッククオートでくくってみた。


と書いていたんだけど、見出しの # C# に続けて半角スペースを打てばよいという編集リクエストを @htsign さんから頂いたので修正した。

ありがとうございます。

それはさておきソースコード:

まずはソースコード:


C#

using System;

using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;

namespace NanJSON
{
class Hoge
{
static void Main()
{
Test(Double.NaN);
Test(Double.PositiveInfinity);
}

private static void Test(double val)
{
var ary = new Double[] { val };
using (var ms = new MemoryStream())
using (var sr = new StreamReader(ms))
{
var serializer = new DataContractJsonSerializer(typeof(Double[]));
serializer.WriteObject(ms, ary);
ms.Position = 0;
var json = sr.ReadToEnd();
Console.WriteLine("{0} : {1}", val, json);
}
}
}
}


実行するとこうなる:

NaN : [NaN]

Infinity : [INF]

ひどい。JSON として valid ではない。

実行環境は macOS 10.14.3 で、


  • csc: 2.8.2.62916 (2ad4aabc)

  • mono: 5.18.0.268 (tarball Tue Mar 12 08:29:42 GMT 2019)

である。

10分ぐらい調べた範囲では、回避策はない。ひどい。


Python3

まずはソースコード:


python3

import json

import sys

def test(val):
try:
j = json.dumps( [val] )
print( "%s: %s" % ( val, j ) )
except:
print( "%s: %s" % ( val, sys.exc_info()[1] ) )

test( float("nan") )
test( float("infinity") )


実行するとこうなる:

nan: [NaN]

inf: [Infinity]

ひどい。JSON として valid ではない。

Python3 の場合は回避策がある。

j = json.dumps( [val] )



j = json.dumps( [val], allow_nan=False )

に変更すればよい。

そうすると、出力は

nan: Out of range float values are not JSON compliant

inf: Out of range float values are not JSON compliant

になる。正しい。なんでこちらがデフォルトじゃないんだろう。


Perl

まずはソースコード:


perl

use strict;

use warnings;
use utf8;
use JSON;

sub test{
my $val = shift;
my $json = encode_json( [$val] );
print( $val, ":", $json, "\n" );
}

my $inf = 9**9**9;
my $nan = $inf - $inf;
test($nan);
test($inf);


無限大と非数の定数や関数がないらしい。

これを実行するとこうなる:

nan:[nan]

inf:[inf]

これもひどい。

ほとんど調べてないけど、パット見 回避策はなさそう。

ちなみに perl は v5.18.2


PHP

まずはソースコード:


PHP7

<?php

function test($val)
{
$json = json_encode([$val]);
echo $val, ": ";
if (is_string($json)){
echo $json, "\n";
} else {
var_export($json);
echo " / ";
var_export(json_last_error_msg());
echo "\n";
}
}

test( INF );
test( NAN );


動かすとこうなる:

INF: false / 'Inf and NaN cannot be JSON encoded'

NAN: false / 'Inf and NaN cannot be JSON encoded'

ドキュメントを見ると


成功した場合に、JSON エンコードされた文字列を返します。 失敗した場合に FALSE を返します。


という PHP のことをよく知らない私にはやや意外な I/F になっていて、それがちゃんと機能しているようだ。

初出時


とはいえ、エラーの原因などについて知る方法はなさそうな感じ。残念。


と書いていたが、エラーを取る関数を間違えていただけだった。実際は上記の通り(そして @rana_kualu さんのコメントの通り) エラーの原因も取れる。

さらに、そういうオプションを指定すれば例外にすることもできるようだ。

素晴らしい。

ちなみに、上記ソースコードで if (is_string($json)) となっている箇所を if ($json) としてはいけない。

json_encode(0) は、"0" であり、 "0" は falsy なので不幸になる。


C++

C++ には私の知る限り標準的な JSON encoder / parse はない。

よく使われていそうな JSON ライブラリがどうしているのか、二例調査した。


picojson

仕事で使ったことがある。ヘッダのみなので導入が楽。

で。これを使ったソースコード:


c++17

// clang++ -std=c++17 -Wall -O0

#include <iostream>
#include <limits>
#include <string>
#include <vector>
#include "picojson/picojson.h" // https://github.com/kazuho/picojson

void test(double v) {
std::cout << v << ":";
try {
std::vector<picojson::value> ary{picojson::value(v)};
picojson::value obj(ary);
std::cout << obj.serialize() << std::endl;
} catch (std::overflow_error &e) {
std::cout << "overflow error" << std::endl;
}
}

int main() {
test(std::numeric_limits<double>::quiet_NaN());
test(std::numeric_limits<double>::infinity());
}


実行するとこうなる:

nan:overflow error

inf:overflow error

ちゃんと例外になる。

NaN の場合も std::overflow_error なのはどうなんだとか、


throw std::overflow_error("");


と、メッセージなしなのはちょっととか、思わなくもないけれど、困ることはないと思う。


json11

picojson の他にライブラリ無いかなと思って探して最初に見つけたもの。

というわけでソースコード:


c++17

// clang++ -std=c++17 -Wall -O0

#include <iostream>
#include <limits>
#include <string>
#include <vector>
#include "json11/json11.hpp" // https://github.com/dropbox/json11/

void test(double v) {
std::cout << v << ":";
try {
json11::Json o = json11::Json::array{v};
std::cout << o.dump() << std::endl;
} catch (std::exception &e) {
std::cout << e.what() << std::endl;
}
}

int main() {
test(1);
test(std::numeric_limits<double>::quiet_NaN());
test(std::numeric_limits<double>::infinity());
}


実行するとこうなる:

nan:[null]

inf:[null]

だまって null にされる。

std::isfinite を呼んでいる場所 を見る限りオプションなどはない模様。


まとめ

各言語の対応をまとめると:

言語
対応
評価(私見)

Ruby
例外
Very Good

Go
エラー
Very Good

JavaScript(node)

null に変換する
微妙

C#
NaN, INF にする
だめ

Python3
NaN, Infinity にする(回避可能)
ややだめ

Perl
nan, inf にする
だめ

PHP
失敗を意味する FALSE を返す。別途関数で原因も取れる。
Very Good

C++ + picojson
例外
Good

C++ + json11

null に変換する
微妙

という感じ。

※ 初出時 PHP は「Good」だったが、ちゃんと原因も取れるようなので「Very Good」に変更した。

だめな人が多くてびっくりした。