JSON形式でのデータの書き込み

JSON形式でのデータの書き込み



私のプログラムの1つで、 JSON形式でデータを書き込む必要がありました。 つまり、XMLに似た形式であり、WindowsをINIファイルまたは同じXMLで置き換えるのに非常に適しています。 配列と独自の構造のネストをサポートするという点で便利ですが、人間が完全に判読できなくなるまでデータファイルにタグが散らばることはありません。 データファイルの例を次に示します。

{ "Comment":"My comment", "Count":10, "DiskParam": { "DB":10.000000, "DBAngle":1.234000 }, "Range":true, "Blades": [ { "Caption":"A", "Value":65 }, { "Caption":"B", "Value":66 }, { "Caption":"C", "Value":67 } ], "Slots": [ 0,1,2 ] }
      
      





形式は非常にシンプルで、ライブラリなしで作業することが可能です。 そのため、最初のコードが記録を担当しました。

  fprintf(pOut, "{\n"); fprintf(pOut, " \"Comment\":\"%s\"", Header->Comment); fprintf(pOut, ",\n \"NumSt\":%d", Header->NumSt); //   fprintf(pOut, ",\n \"DBMax\":%lf", Header->DBMax); fprintf(pOut, ",\n \"Range\":%s", Header->Range?"true":"false"); fprintf(pOut, ",\n \"Blades\":\n ["); for(int i=0; i<Header->Count; i++) { TElement &e=Element[i]; fprintf(pOut, i?",\n {":"\n {"); fprintf(pOut, "\"Caption\":\"%s\"", e.Caption); fprintf(pOut, ",\"Value\":%lf", e.BaseChar); fprintf(pOut, "}"); } fprintf(pOut, "\n ]"); //   fprintf(pOut, "\n}");
      
      





Koryavenko、それは非常に機能的ですが。 しかし、プログラムは積極的に終了し、データ形式は1日に5回変更され、すべての変更を追跡するという深刻な問題がありました。 ソースの書式設定は多少ありますが、データファイル自体を書式設定するために、タグを閉じるか、必要な数のスペースを正しく印刷することを忘れないようにしてください。 上記のフラグメントでさえ、公開前にエラーが検出されました;コンマは配列の要素の間に置かれませんでした。



私はこのプロセスをわずかに機械化し、JSONを操作するためのマイクロライブラリを作成することにしました。



何が欲しかった? だから私のプログラムでは、私は擬似言語で何かを書きます:

 Key("1"); Value("1"); Key("2"); Value("2"); Object("1"); Key("3"); Value("3"); //3,4   1 Key("4"); Value("4"); Array("1"); Key("5"); Value("5"); //5...N   1 Key("6"); Value("6"); ... Key("N"); Value("N");
      
      





そして、コンパイラー/プログラムに、データファイルの構造を決定するインデントを考慮させます。 適切なタイミングで、開始タグ、および最も重要な終了タグを置き換えます。 問題は、このスクリプト内でC ++コンストラクト、たとえば配列内のループを使用したいという事実によって複雑になりました。



この問題を数日間連続して脳で包囲した後、かなりエレガントな解決策が見つかりました。 JSONエンティティの相互への埋め込みとタグのタイムリーな終了を制御するために、変数のスコープが使用されます。 すべてが非常に単純で、TJson ***クラスの1つのインスタンスが作成されます-キーと開始タグが書き込まれ、作成された以下のオブジェクトはすべてその添付ファイルと見なされます。 インスタンスは破棄されます-終了タグが置かれます。

 #define TCF_USED 1 class TTagCloser { public: TTagCloser *Owner; static TTagCloser *Current; static int Depth; int Flags; int Count; int operator()(){Flags^=TCF_USED; return Flags&TCF_USED;} TTagCloser(){Count=Flags=0; Owner=Current; Current=this; Depth++;} ~TTagCloser(){Depth--; Current=Owner;} }; TTagCloser *TTagCloser::Current=NULL; int TTagCloser::Depth=-1;
      
      





生成されたオブジェクトを一時的に一種のツリーにリンクすることが全体的な目的である単純なクラス。 オーバーロードされた演算子()が必要な理由は、後ほど明らかになります。



このクラスには、JSON形式の基本的な書き込み機能が組み込まれた相続人がいます。 プログラマは、Write ***関数のみをオーバーライドする必要があります。

 #define TCF_OBJECT 4 #define TCF_ARRAY 2 class TJsonTagCloser:public TTagCloser { public: void WriteTab(); void WriteInt(int); void WriteDouble(double); void WriteStr(char *); TJsonTagCloser(char *Key); }; //---------------------------------------------------------------------------- TJsonTagCloser::TJsonTagCloser(char *Key):TTagCloser() { if(Owner) { if(Owner->Count) WriteStr(","); if(Owner->Flags&TCF_ARRAY) { if(!Owner->Count) WriteTab(); } else { WriteTab(); WriteStr("\""); if(Key) WriteStr(Key); WriteStr("\":"); } Owner->Count++; } }
      
      





WriteTab()関数は、メモ帳データファイルに登りたいオタク向けの便利なプログラムに導入されています。 データファイルの添付の深さに対応する改行とスペースの数(TTagCloser :: Depth)を書き込む必要があります。 フォーマットが不要な場合、関数はWriteTab(){;}に縮退します。



私のテストケースでは、Write ***関数は次のように定義されています。

 #include <stdio.h> void TJsonTagCloser::WriteTab(){printf("\n%*s", Depth*2, "");} void TJsonTagCloser::WriteInt(int Value){printf("%d", Value);} void TJsonTagCloser::WriteDouble(double Value){printf("%lf", Value);} void TJsonTagCloser::WriteStr(char *Value){printf("%s", Value);}
      
      





JSON形式は、オブジェクト(SYN構造のように見える)、配列(アフリカの配列でもあります)、および「キー:値」のペアのデータストリームに存在することを前提としています。 このすべての多様性は、たとえば「キー:値」のペアで混在してネストすることができます。値はオブジェクトの配列にすることができます。 これらのエンティティを操作するために、次のクラスが作成されました。

 class TJsonArray:public TJsonTagCloser { public: TJsonArray(char *Key); ~TJsonArray(); }; class TJsonObject:public TJsonTagCloser { public: TJsonObject(char *Key); ~TJsonObject(); }; class TJsonValue:public TJsonTagCloser { public: TJsonValue(char *Key, int Value):TJsonTagCloser(Key){WriteInt (Value);} TJsonValue(char *Key, double Value):TJsonTagCloser(Key){WriteDouble(Value);} TJsonValue(char *Key, bool Value):TJsonTagCloser(Key){WriteStr((char *)(Value?"true":"false"));} TJsonValue(char *Key, char *Value); }; TJsonArray::TJsonArray(char *Key):TJsonTagCloser(Key) { Flags|=TCF_ARRAY; if(Owner && (!(Owner->Flags&TCF_ARRAY) || Owner->Count>1)) WriteTab(); WriteStr("["); } TJsonArray::~TJsonArray() { WriteTab(); WriteStr("]"); } //---------------------------------------------------------------------------- TJsonObject::TJsonObject(char *Key):TJsonTagCloser(Key) { Flags|=TCF_OBJECT; if(Owner && (!(Owner->Flags&TCF_ARRAY) || Owner->Count>1)) WriteTab(); WriteStr("{"); } TJsonObject::~TJsonObject() { WriteTab(); WriteStr("}"); } TJsonValue::TJsonValue(char *Key, char *Value):TJsonTagCloser(Key) { if(Value) { WriteStr("\""); WriteStr(Value); WriteStr("\""); } else WriteStr("null"); }
      
      





ライブラリを使用するために、プログラムでマクロが定義されています。

 #define ARRAY(k) for(TJsonArray array(k); array();) #define OBJECT(k) for(TJsonObject object(k); object();) #define VALUE(k,v) {TJsonValue value(k,v);}
      
      





オーバーロードされた演算子()に到達しました。 forループの本体を1回実行するために必要です。つまり、最初の呼び出しでtrueを返し、次の呼び出しでfalseを返します。



そして、これは、スクリプトがプログラムの本体でどのように見えるかであり、データファイルの内容が書き込まれます。

 void main() { OBJECT("") { VALUE("Comment", "My comment"); VALUE("Count", 10); OBJECT("DiskParam") { VALUE("DB", 10.0); VALUE("DBAngle", 1.234); } VALUE("Range", true); ARRAY("Blades") { for(int i='A'; i<'A'+3; i++) OBJECT("") { VALUE("Caption", (char *)&i); VALUE("Value", i); } } ARRAY("Slots") for(int i=0; i<3; i++) VALUE("", i); } }
      
      





このプログラムによって生成されたJSONファイルは、記事の冒頭で見ることができます。 すべてのコンマが添付され、必要に応じてすべてのブラケットが閉じられます。各行の先頭のスペースの量は適切です!



All Articles