まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

タグ:post

以前からなんとなくこの挙動に気付いてはいたのですが、データを受け取る側で加工することで回避できていたので真剣に原因や対処を考えていませんでした。今回とある機能をライブラリ化することになり、「受け取る側ではなく、送る側で対処が必要」になったので真面目に調査して、対処法含めて分かったのでブログに残しておくことにしました。

現象はこのブログエントリのタイトル通りなのですが、まず「何をするとどうなるか」を紹介します。例えば jQuery を使ったこんな内容のコードを実行したとします:
$.ajax({
  url: '/api/item',
  type: 'POST',
  data: { name: 'シャンプー', price: 1000 },
  success: function( result ){
    :
  },
  error: function( err, text, message ){
    :
  }
});

Ajax を使って /api/item に POST リクエストを実行する、という内容です。その際に { name: 'シャンプー', price: 1000 } という JSON データを送信しています。

これを以下のような Node.js のコードで受け取った場合を想定してみます(Node.js はあくまでコードの例であって、Node.js じゃなくても同じ現象が起こります):
var express = require( 'express' ),
    bodyParser = require( 'body-parser' ),
    app = express();

app.use( bodyParser.urlencoded( { extended: true } );
app.use( bodyParser.json() );

  :

app.post( '/api/item', function( req, res ){
  var data = req.body;
  console.log( data );

    :
});



Node.js ではよく使う方法ですが、Express (と body-parser)を使って "/api/item" に POST されたデータを受け取っています("var data = req.body;")。この例では受け取った内容をそのままコンソールに表示しています("console.log( data );")。

先ほどの POST データであれば、コンソール画面には
{ name: "シャンプー", price: 1000 }

という JSON データが表示されるような気がしますが、実際には
{ name: "シャンプー", price: "1000" }

という、price の値が送信した時の数値ではなく、文字列に変換されて送信されます。これが本ブログエントリのタイトルにも書いた『jQuery の $.ajax で JSON データを POST すると整数が文字列になる??』という現象です。

送る側だけでなく、受け取る側も自分達が実装しているのであれば受け取った JSON データの price の値を数値に変換するという手もあります:
//. data.price が文字列だった場合は数値に変換する
if( typeof data.price == 'string' ){
  data.price = parseInt( data.price ); 
}

最悪、この方法で対処できるかもしれませんが、受け取る側を変更できないという前提にした場合、この方法は使えなくなります。つまり「正しいフォーマットで送信する」必要がでてきます。

で、いろいろ調べた結果として、jQuery の Ajax 実行部を以下のように変更することで JSON データ内のフォーマットを壊さずに、JSON データのまま送信することができるようでした:
$.ajax({
  url: '/api/item',
  type: 'POST',
  contentType: 'application/json',
  dataType: 'json',
  data: JSON.stringify( { name: 'シャンプー', price: 1000 } ),
  success: function( result ){
    :
  },
  error: function( err, text, message ){
    :
  }
});

先ほどの例と違う部分を赤字にしていますが、要するに、
  • contentType: 'application/json' を追加する
  • dataType: 'json' を追加する
  • 送信する JSON データ全体を JSON.stringify() で文字列化する
という3つの変更を加えることでそのままのフォーマットで(受信側でも price は文字列の '1000' ではなく、整数の 1000 として)送信することができるようでした。


そういえば以前はこのスタイルで送っていたような気がしつつ、よりシンプルな方法でも(データ型が乱れるだけで)送れることに気付いて、それから省略形で送ってしまっていたような気がします。その結果、データのフォーマットがおかしくなっていた(が、あまり気にしなかった)ということだと思っています。 ともあれ、これで受け取る側を変更しなくても、正しいデータフォーマットのままで送信することができそうです。

jQuery の場合「正確に記述しなくてもある程度できちゃう」から、正確でなかった今までの方法が正しいと勘違いしていたようです。で、足りない部分もちゃんと記述すればできる、と。いい勉強になりました。



久しぶりの Java プログラミングの機会があり、これまた久しぶりにサーブレットを作りました。POST されたデータを受け取って、バイナリデータを生成して、Content-Type をつけてストリームで返す、というものです。

この POST データの受け取り方をどうするかで(少しだけ)迷ったのですが、変数もその型も数も不定で受け取るような仕様だったので、深く考えずに JSON データを受け取ることにしました。普段の Node.js の時は特殊な事情がない限り JSON で受け取ることにしているので、その延長というか「まあイマドキは JSON だよね」くらいに考えていました。

・・・が、これが意外と苦戦。 誰かの参考になれば・・・、と思って、以下実際に作ったサーブレットの実装を紹介します。

まず最初はクライアント(呼び出し)側は普通にこんな感じの実装にしました。jQuery の AJAX を使ってタイムスタンプ値を含む JSON オブジェクトをポストしています。これを受け取って処理するサーブレット(/postdata)を作るのが今回の目的となります:
  :

$.ajax({
  type: 'POST',
  url: './postdata',
  data: { timestamp: ( new Date() ).getTime(), body: 'ハローワールド' },
  success: function( result ){
    console.log( result );
  },
  error: function( err ){
    console.log( 'error' );
  }
});
:

そしてそのサーブレットのコード部分が以下です:
public class PostdataServlet extends HttpServlet {
	
  @Override
  protected void doPost( HttpServletRequest req, HttpServletResponse res )
throws ServletException, IOException {
req.setCharacterEncoding( "UTF-8" ); try{
//. JSON テキストを全部取り出す BufferedReader br = new BufferedReader( req.getReader() ); String jsonText = br.readLine(); jsonText = URLDecoder.decode( json, "UTF-8" ); //System.out.println( jsonText );
//. JSON オブジェクトに変換 JSONParser parser = new JSONParser(); JSONObject jsonObj = ( JSONObject )parser.parse( jsonText );
//. JSON オブジェクトから特性の属性を取り出す
String body = ( String )jsonObj.get( "body" );
: }catch( Exception e ){ e.printStackTrace(); } } }

要は req.getParameter() などを使って特定のパラメータ値を取り出すわけではなく、req.getReader() を使ってポストされてきた全データ(今回の場合は JSON オブジェクトをテキスト化したもの)を受け取る必要があります。そして受け取ったテキストデータを(上記の例であれば JSON-Simple ライブラリを使って)JSON オブジェクト化した上でサーブレット内で取り扱う、という手法です。


・・・単純に JSON データを扱いたかっただけなんだけど、こんなに面倒だったっけ?

 

このページのトップヘ