1 /******************************************************************************
2 
3     Toolkit to extract values from JSON content of an expected structure.
4 
5     Usage example:
6 
7     ---
8 
9     const content =
10     `{`
11         `"id":"8c97472e-098e-4baa-aa63-4a3f2aab10c6",`
12         `"imp":`
13         `[`
14             `{`
15                  `"impid":"7682f6f1-810c-49b0-8388-f91ba4a00c1d",`
16                  `"h":480,`
17                  `"w":640,`
18                  `"btype": [ 1,2,3 ],`
19                  `"battr": [ 3,4,5 ]`
20             `}`
21         `],`
22         `"site":`
23         `{`
24 
25             `"sid":"1",`
26             `"name":"MySite",`
27             `"pub":"MyPublisher",`
28             `"cat": [ "IAB1", "IAB2" ],`
29             `"page":"http://www.example.com/"`
30         `},`
31         `"user":`
32         `{`
33             `"uid":"45FB778",`
34             `"buyeruid":"100"`
35         `},`
36         `"device":`
37         `{`
38             `"ip":"192.168.0.1",`
39             `"ua":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.30 `
40                   `(KHTML, like Gecko) Chrome/12.0.742.53 Safari/534.30"`
41         `},`
42         `"cud":`
43         `{`
44             `"age":"23",`
45             `"gender":"female"`
46         `}`
47     `}`;
48 
49     // Aliases to avoid polluting this example with dozens of "JsonExtractor.".
50 
51     alias JsonExtractor.Parser    Parser;   // actually aliases JsonParserIter
52     alias JsonExtractor.GetField  GetField;
53     alias JsonExtractor.GetObject GetObject;
54     alias JsonExtractor.GetArray  GetArray;
55     alias JsonExtractor.Main      Main;
56     alias JsonExtractor.Type      Type;     // actually aliases JsonParser.Token
57 
58     // Create JSON parser instance.
59 
60     Parser json = new Parser;
61 
62     // Create one GetField instance for each JSON object field to extract.
63 
64     GetField id    = new GetField,
65              impid = new GetField,
66              page  = new GetField,
67              uid   = new GetField,
68              h     = new GetField,
69              w     = new GetField;
70 
71     // Create one GetObject instance for each JSON subobject that contains
72     // fields to extract and pass an associative array of name/GetField
73     // instance pairs to define the fields that should be extracted in this
74     // subobject.
75 
76     GetObject site = new GetObject(json, ["page": page]),
77               user = new GetObject(json, ["uid": uid]),
78                             // cast needed to prevent array type inference error
79        imp_element = new GetObject(json, ["impid"[]: impid, "w": w, "h": h]);
80 
81 
82     // Create one IterateArray instance for each JSON array that contains
83     // members to extract.
84 
85     GetArray imp = new GetArray(json, [imp_element]
86                                (uint i, Type type, cstring value)
87                                {
88                                    // This delegate will be called for each
89                                    // "imp" array element with i as index. Note
90                                    // that value is meaningful only if type is
91                                    // type.String or type.Number.
92                                    // We are interested in the first array
93                                    // element only, which we expect to be an
94                                    // object, so we call imp_element.set() when
95                                    // i is 0. We return true if we handle the
96                                    // element or false to make imp skip it.
97 
98                                    bool handled = i == 0;
99 
100                                    if (handled)
101                                    {
102                                        if (type == type.BeginObject)
103                                        {
104                                            imp_element.set(type);
105                                        }
106                                        else throw new Exception
107                                        (
108                                            "\"imp\" array element is not an "
109                                            "object as expected!"
110                                        );
111                                    }
112 
113                                    return handled;
114                                });
115 
116     // Create a Main (GetObject subclass) instance for the main JSON object and
117     // pass the top level getters.
118 
119     Main main = new Main(json, ["id"[]: id, "imp": imp, "site": site,
120                                 "user": user]);
121 
122     // Here we go.
123 
124     main.parse(content);
125 
126     // id.type  is now Type.String
127     // id.value is now "8c97472e-098e-4baa-aa63-4a3f2aab10c6"
128 
129     // impid.type  is now Type.String
130     // impid.value is now "7682f6f1-810c-49b0-8388-f91ba4a00c1d"
131 
132     // page.type  is now Type.String
133     // page.value is now "http://www.example.com/"
134 
135     // uid.type  is now Type.String
136     // uid.value is now "45FB778"
137 
138     // h.type  is now Type.Number
139     // h.value is now "480"
140 
141     // w.type  is now Type.Number
142     // w.value is now "640"
143 
144     ---
145 
146     Copyright:
147         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
148         All rights reserved.
149 
150     License:
151         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
152         Alternatively, this file may be distributed under the terms of the Tango
153         3-Clause BSD License (see LICENSE_BSD.txt for details).
154 
155  ******************************************************************************/
156 
157 module ocean.text.json.JsonExtractor;
158 
159 
160 import ocean.meta.types.Qualifiers;
161 
162 import ocean.text.json.JsonParserIter;
163 import ocean.core.Array;
164 import ocean.core.Enforce : enforce;
165 import ocean.core.Test;
166 import ocean.core.Verify;
167 import ocean.util.ReusableException;
168 
169 
170 /*******************************************************************************
171 
172     Exception which only can be thrown by JsonExtractor
173 
174 *******************************************************************************/
175 
176 private class JsonException : ReusableException {}
177 
178 
179 struct JsonExtractor
180 {
181     static:
182 
183     /**************************************************************************
184 
185         Type aliases for using code
186 
187      **************************************************************************/
188 
189     alias JsonParserIter!(false) Parser;
190 
191     alias JsonParserIter!(false).Token Type;
192 
193     /***************************************************************************
194 
195         JSON main/top level object getter
196 
197      **************************************************************************/
198 
199     class Main : GetObject
200     {
201         /***********************************************************************
202 
203             JSON parser instance
204 
205          **********************************************************************/
206 
207         private Parser json;
208 
209         /***********************************************************************
210 
211             Constructor, specifies getters for named and unnamed fields.
212 
213             If the i-th object field is not named and the i-th instance element
214             in get_indexed_fields is not null, it will be invoked with that
215             field.
216 
217             Params:
218                 json               = JSON parser
219                 get_named_fields   = list of getters for named fields,
220                                      associated with field names
221                 get_indexed_fields = list of getters for fields without name,
222                                      may contain null elements to ignore fields.
223 
224          **********************************************************************/
225 
226         public this ( Parser json, GetField[cstring] get_named_fields,
227                       GetField[] get_indexed_fields ... )
228         {
229             super(this.json = json, get_named_fields, get_indexed_fields);
230         }
231 
232         /***********************************************************************
233 
234             Resets all type/value results and parses content, extracting types
235             and values for the fields to extract.
236 
237             Params:
238                 content = JSON content to parse
239 
240             Returns:
241                 true on success or false otherwise.
242 
243             Throws:
244                 Propagates exceptions thrown in
245                 ocean.text.json.JsonParser.parse().
246 
247          **********************************************************************/
248 
249         bool parse ( cstring content )
250         {
251             super.reset();
252 
253             bool ok = this.json.reset(content);
254 
255             if (ok)
256             {
257                 super.set(this.json.type);
258             }
259 
260             return ok;
261         }
262     }
263 
264     /***************************************************************************
265 
266         JSON field getter, extracts type and value of a field.
267 
268      **************************************************************************/
269 
270     class GetField
271     {
272         /**********************************************************************
273 
274             Field type
275 
276          **********************************************************************/
277 
278         Type type;
279 
280         /***********************************************************************
281 
282             Field value, meaningful only for certain types, especially
283             Type.String and Type.Number. Corresponds to the value returned by
284             JsonParser.value() for this field.
285 
286          **********************************************************************/
287 
288         public cstring value = null;
289 
290         /***********************************************************************
291 
292             Sets type and value for the field represented by this instance.
293 
294             Params:
295                 type  = field type
296                 value = field value if meaningful (depends on type)
297 
298          **********************************************************************/
299 
300         final void set ( Type type, cstring value = null )
301         {
302             this.type  = type;
303             this.value = value;
304             this.set_();
305         }
306 
307         /**********************************************************************
308 
309             Resets type and value.
310 
311          **********************************************************************/
312 
313         final void reset ( )
314         {
315             this.type  = this.type.init;
316             this.value = null;
317             this.reset_();
318         }
319 
320         /***********************************************************************
321 
322             To be overridden, called when set() has finished.
323 
324          **********************************************************************/
325 
326         protected void set_ ( ) { }
327 
328         /***********************************************************************
329 
330             To be overridden, called when reset() has finished.
331 
332          **********************************************************************/
333 
334         protected void reset_ ( ) { }
335     }
336 
337     /**************************************************************************
338 
339         JSON object getter, invokes registered field getters with type and value
340         of the corresponding fields in a JSON object.
341 
342      **************************************************************************/
343 
344     class GetObject : IterateAggregate
345     {
346         /***********************************************************************
347 
348             If enabled, any unmatched field will result in an exception.
349 
350          *********************************************************************/
351 
352         public bool strict;
353 
354         /***********************************************************************
355 
356             List of getters for named fields, each associated with the name of a
357             field.
358 
359          **********************************************************************/
360 
361         private GetField[cstring] get_named_fields;
362 
363         /***********************************************************************
364 
365             List of getters for fields without name, may contain null elements
366             to ignore fields.  If the i-th object field is not named and the
367             i-th instance element is not null, it will be invoked with that
368             field.
369 
370          **********************************************************************/
371 
372         private GetField[]       get_indexed_fields;
373 
374 
375         /***********************************************************************
376 
377             Thrown as indicator when strict behavior enforcement fails.
378 
379          *********************************************************************/
380 
381         private JsonException field_unmatched;
382 
383         /***********************************************************************
384 
385             Constructor, specifies getters for named and unnamed fields.
386 
387             If the i-th object field is not named and the i-th instance element
388             in get_indexed_fields is not null, it will be invoked with that
389             field.
390 
391             Params:
392                 json               = JSON parser
393                 get_named_fields   = list of getters for named fields,
394                                      associated with field names
395                 get_indexed_fields = list of getters for fields without name,
396                                      may contain null elements to ignore fields.
397 
398          **********************************************************************/
399 
400         public this ( Parser json, GetField[cstring] get_named_fields,
401                       GetField[] get_indexed_fields ... )
402         {
403             this(json, false, get_named_fields, get_indexed_fields);
404         }
405 
406         /***********************************************************************
407 
408             Constructor, specifies getters for named and unnamed fields.
409 
410             If the i-th object field is not named and the i-th instance element
411             in get_indexed_fields is not null, it will be invoked with that
412             field.
413 
414             Params:
415                 json               = JSON parser
416                 skip_null          = should a potential null value be skipped?
417                 get_named_fields   = list of getters for named fields,
418                                      associated with field names
419                 get_indexed_fields = list of getters for fields without name,
420                                      may contain null elements to ignore fields.
421 
422          **********************************************************************/
423 
424         public this ( Parser json, bool skip_null,
425                       GetField[cstring] get_named_fields,
426                       GetField[] get_indexed_fields ... )
427         {
428             super(json, Type.BeginObject, Type.EndObject, skip_null);
429 
430             this.field_unmatched = new JsonException();
431             this.get_named_fields = get_named_fields.rehash;
432             this.get_indexed_fields = get_indexed_fields;
433         }
434 
435         /***********************************************************************
436 
437             Add the field to the list of named objects to get.
438 
439             Params:
440                 name = the name of the field
441                 field = the field instance
442 
443          **********************************************************************/
444 
445         public void addNamedField ( cstring name, GetField field )
446         {
447             this.get_named_fields[name] = field;
448         }
449 
450         /***********************************************************************
451 
452             Remove a field from the list of named objects to get.
453 
454             Params:
455                 name = the name of the field
456 
457          **********************************************************************/
458 
459         public void removeNamedField ( cstring name )
460         {
461             this.get_named_fields.remove(name);
462         }
463 
464         /***********************************************************************
465 
466             Called by super.reset() to reset all field getters.
467 
468          **********************************************************************/
469 
470         protected override void reset_ ( )
471         {
472             foreach (get_field; this.get_named_fields)
473             {
474                 get_field.reset();
475             }
476 
477             foreach (get_field; this.get_indexed_fields)
478             {
479                 get_field.reset();
480             }
481         }
482 
483         /***********************************************************************
484 
485             Called by super.reset() to reset all field getters.
486 
487          **********************************************************************/
488 
489         protected override void set_ ( )
490         {
491             super.set_();
492 
493             if (this.strict)
494             {
495                 foreach (name, field; this.get_named_fields)
496                 {
497                     if (field.type == Type.Empty)
498                     {
499                         throw this.field_unmatched
500                             .set("Field '")
501                             .append(name)
502                             .append("' not found in JSON");
503                     }
504                 }
505 
506                 foreach (i, field; this.get_indexed_fields)
507                 {
508                     if (field.type == Type.Empty)
509                     {
510                         throw this.field_unmatched
511                             .set("Unnamed field not found in JSON");
512                     }
513                 }
514             }
515         }
516 
517         /***********************************************************************
518 
519             Picks the field getter responsible for the field corresponding to
520             name, or i if unnamed, and sets its type and value.
521 
522             Params:
523                 i     = field index
524                 type  = field type
525                 name  = field name or null if unnamed.
526                 value = field value, meaningful only for certain types.
527 
528             Returns:
529                 true if a getter handled the field or false to skip it.
530 
531          **********************************************************************/
532 
533         protected override bool setField ( uint i, Type type, cstring name, cstring value )
534         {
535             GetField get_field = this.getGetField(i, name);
536 
537             bool handle = get_field !is null;
538 
539             if (handle)
540             {
541                 get_field.set(type, value);
542             }
543 
544             return handle;
545         }
546 
547         /***********************************************************************
548 
549             Picks the field getter responsible for the field corresponding to
550             name, or i if unnamed.
551 
552             Params:
553                 i     = field index
554                 name  = field name or null if unnamed
555 
556             Returns:
557                 GetField instance responsible for the field or null if there is
558                 no responsible getter.
559 
560          **********************************************************************/
561 
562         private GetField getGetField ( uint i, cstring name )
563         {
564             GetField* get_field = name?
565                                     name in this.get_named_fields :
566                                     (i < this.get_indexed_fields.length)?
567                                         &this.get_indexed_fields[i] :
568                                         null;
569 
570             return get_field? *get_field : null;
571         }
572     }
573 
574     /**************************************************************************
575 
576         JSON array getter, invokes a callback delegate with each element in a
577         JSON array.
578 
579      **************************************************************************/
580 
581     class GetArray : IterateArray
582     {
583         /***********************************************************************
584 
585             Iteration callback delegate type alias. The delegate must either use
586             an appropriate GetField (or subclass) instance to handle and move
587             the parser to the end of the field or indicate that this field is
588             ignored and unhandled.
589 
590             Params:
591                 i     = element index counter, starts with 0
592                 type  = element type
593                 value = element value, meaningful only for certain types.
594 
595             Returns:
596                 true if an appropriate GetField (or subclass) instance was used
597                 to handle and move the parser to the end of the field or false
598                 if the field is ignored and unhandled and should be skipped.
599 
600          **********************************************************************/
601 
602         public alias bool delegate ( uint i, Type type, cstring value) IteratorDg;
603 
604         /***********************************************************************
605 
606             Iteration callback delegate
607 
608          **********************************************************************/
609 
610         private IteratorDg iterator_dg;
611 
612         /***********************************************************************
613 
614             List of fields to reset when this.reset is called.
615 
616          **********************************************************************/
617 
618         private GetField[] fields_to_reset;
619 
620         /***********************************************************************
621 
622             Constructor
623 
624             Params:
625                 json            = JSON parser
626                 fields_to_reset = fields to reset when this.reset is called
627                 iterator_dg     = iteration callback delegate
628                 skip_null       = should a potential null value be skipped? If
629                                   false and a null value is found a
630                                   JsonException will be thrown.
631 
632          **********************************************************************/
633 
634         public this ( Parser json, GetField[] fields_to_reset,
635                       scope IteratorDg iterator_dg, bool skip_null = false )
636         {
637             super(json, skip_null);
638 
639             this.fields_to_reset = fields_to_reset;
640 
641             this.iterator_dg = iterator_dg;
642         }
643 
644         /***********************************************************************
645 
646             Invokes the iteration callback delegate.
647 
648             Params:
649                 i     = field index
650                 type  = field type
651                 name  = (ignored)
652                 value = field value
653 
654             Returns:
655                 passes through the return value of the delegate.
656 
657          **********************************************************************/
658 
659         protected override bool setField ( uint i, Type type, cstring name,
660             cstring value )
661         {
662             return this.iterator_dg(i, type, value);
663         }
664 
665 
666         /***********************************************************************
667 
668             Called by super.reset() to reset all field given by fields_to_reset.
669 
670          **********************************************************************/
671 
672         protected override void reset_ ( )
673         {
674             foreach (get_field; this.fields_to_reset)
675             {
676                 get_field.reset();
677             }
678         }
679     }
680 
681     /**************************************************************************
682 
683         Abstract JSON array iterator. As an alternative to the use of an
684         iteration callback delegate with GetArray one can derive from this
685         class and implement setField().
686 
687      **************************************************************************/
688 
689     abstract class IterateArray : IterateAggregate
690     {
691         /***********************************************************************
692 
693             Constructor
694 
695             Params:
696                 type = expected parameter type
697                 key  = parameter name
698                 skip_null  = should a potential null value be skipped? If false
699                              and a null value is found an JsonException will
700                              be thrown.
701 
702          **********************************************************************/
703 
704         public this ( Parser json, bool skip_null = false )
705         {
706             super(json, Type.BeginArray, Type.EndArray, skip_null);
707         }
708     }
709 
710     /**************************************************************************
711 
712         JSON object or array iterator.
713 
714      **************************************************************************/
715 
716     abstract class IterateAggregate : GetField
717     {
718 
719         /***********************************************************************
720 
721             Skip null value?
722 
723          **********************************************************************/
724 
725         private bool skip_null;
726 
727         /**********************************************************************
728 
729             Start and end token type, usually BeginObject/EndObject or
730             BeginArray/EndArray.
731 
732          **********************************************************************/
733 
734         public Type start_type, end_type;
735 
736         /***********************************************************************
737 
738             JSON parser instance
739 
740          **********************************************************************/
741 
742         private Parser json;
743 
744         /***********************************************************************
745 
746             Exception throw to indicate errors during parsing.
747 
748          **********************************************************************/
749 
750         protected JsonException exception;
751 
752         /***********************************************************************
753 
754             Constructor
755 
756             Params:
757                 json       = JSON parser, can't be null
758                 start_type = opening token type of the aggregate this instance
759                              iterates over (usually BeginObject or BeginArray)
760                 end_type   = closing token type of the aggregate this instance
761                              iterates over (usually EndObject or EndArray)
762                 skip_null  = should a potential null value be skipped? If false
763                              and a null value is found an AssertException will
764                              be thrown.
765 
766          **********************************************************************/
767 
768         public this ( Parser json, Type start_type, Type end_type,
769                       bool skip_null = false )
770         {
771             verify(json !is null);
772             this.start_type = start_type;
773             this.end_type   = end_type;
774             this.json       = json;
775             this.exception  = new JsonException();
776             this.skip_null  = skip_null;
777         }
778 
779         /***********************************************************************
780 
781             Invoked by super.set() to iterate over the JSON object or array.
782             Expects the type of the current token to be
783              - the start type if this.skip_null is false or
784              - the start type or null if this.skip_null is true.
785 
786             Throws:
787                 JsonException if the type of the current token is not as
788                 expected.
789 
790          **********************************************************************/
791 
792         protected override void set_ ( )
793         {
794             enforce(this.exception,
795                     (this.type == this.start_type) ||
796                     (this.skip_null && this.type == Type.Null),
797                     "type mismatch");
798 
799             uint i = 0;
800 
801             if (this.json.next()) foreach (type, name, value; this.json)
802             {
803                 if (type == this.end_type)
804                 {
805                     break;
806                 }
807                 else if (!this.setField(i++, type, name, value))
808                 {
809                     this.json.skip();
810                 }
811             }
812         }
813 
814         /***********************************************************************
815 
816             Abstract iteration method, must either use an appropriate GetField
817             (or subclass) instance to handle and move the parser to the end of
818             the field or indicate that this field is ignored and unhandled.
819 
820             Params:
821                 i     = element index counter, starts with 0.
822                 name  = field name or null if the field is unnamed or iterating
823                         over an array.
824                 type  = element type
825                 value = element value, meaningful only for certain types.
826 
827             Returns:
828                 true if an appropriate GetField (or subclass) instance was used
829                 to handle and move the parser to the end of the field or false
830                 if the field is ignored and unhandled and should be skipped.
831 
832          **********************************************************************/
833 
834         abstract protected bool setField ( uint i, Type type, cstring name,
835                                            cstring value );
836     }
837 
838     /**************************************************************************/
839 
840     unittest
841     {
842         enum content =
843         `{
844             "id":"8c97472e-098e-4baa-aa63-4a3f2aab10c6",
845             "imp":
846             [
847                 {
848                      "impid":"7682f6f1-810c-49b0-8388-f91ba4a00c1d",
849                      "h":480,
850                      "w":640,
851                      "btype": [ 1,2,3 ],
852                      "battr": [ 3,4,5 ]
853                 },
854                 {
855                     "Hello": "World!"
856                 },
857                 12345
858             ],
859             "site":
860             {
861                 "sid":"1",
862                 "name":"MySite",
863                 "pub":"MyPublisher",
864                 "cat": [ "IAB1", "IAB2" ],
865                 "page":"http://www.example.com/"
866             },
867             "bcat": null,
868             "user":
869             {
870                 "uid":"45FB778",
871                 "buyeruid":"100"
872             },
873             "device":
874             {
875                 "ip":"192.168.0.1",
876                 "ua":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.30 `
877                    ~ `(KHTML, like Gecko) Chrome/12.0.742.53 Safari/534.30"
878             },
879             "cud":
880             {
881                 "age":"23",
882                 "gender":"female"
883             }
884         }`;
885 
886         auto t = new NamedTest("JsonExtractor");
887 
888 
889         scope json        = new Parser,
890               id          = new GetField,
891               impid       = new GetField,
892               page        = new GetField,
893               uid         = new GetField,
894               h           = new GetField,
895               w           = new GetField,
896               not         = new GetField,
897               site        = new GetObject(json, ["page": page]),
898               user        = new GetObject(json, ["uid": uid]),
899               imp_element = new GetObject(json, ["impid"[]: impid,
900                                                  "w": w]),
901               bcat        = new GetObject(json, true, ["not":not]),
902               imp         = new GetArray(json, [imp_element],
903                                        (uint i, Type type, cstring value)
904                                        {
905                                            bool handled = i == 0;
906 
907                                            if (handled)
908                                            {
909                                                t.test!("==")(type,
910                                                              type.BeginObject);
911                                                imp_element.set(type);
912                                            }
913 
914                                            return handled;
915                                        }),
916            main          = new Main(json, ["id"[]: id, "imp": imp,
917                                            "site": site, "user": user]);
918 
919         imp_element.addNamedField("h", h);
920 
921         bool ok = main.parse(content);
922 
923         t.test(ok, "parse didn't return true");
924 
925         t.test!("==")(id.type, Type.String);
926         t.test!("==")(id.value, "8c97472e-098e-4baa-aa63-4a3f2aab10c6"[]);
927 
928         t.test!("==")(impid.type, Type.String);
929         t.test!("==")(impid.value, "7682f6f1-810c-49b0-8388-f91ba4a00c1d"[]);
930 
931         t.test!("==")(page.type, Type.String);
932         t.test!("==")(page.value, "http://www.example.com/"[]);
933 
934         t.test!("==")(uid.type, Type.String);
935         t.test!("==")(uid.value, "45FB778"[]);
936 
937         t.test!("==")(not.type, Type.Empty);
938         t.test!("==")(not.value, ""[]);
939 
940         t.test!("==")(h.type, Type.Number);
941         t.test!("==")(h.value, "480"[]);
942 
943         t.test!("==")(w.type, Type.Number);
944         t.test!("==")(w.value, "640"[]);
945 
946         imp_element.removeNamedField("h");
947         h.reset();
948 
949         ok = main.parse(content);
950 
951         t.test(ok, "parse didn't return true"[]);
952 
953         t.test!("==")(h.type, Type.Empty);
954         t.test!("==")(h.value, ""[]);
955 
956 
957         ok = main.parse("{}");
958 
959         t.test(ok, "parse didn't return true"[]);
960 
961         t.test!("==")(id.value, ""[]);
962         t.test!("==")(id.type, Type.Empty);
963 
964         t.test!("==")(impid.value, ""[]);
965         t.test!("==")(impid.type, Type.Empty);
966 
967         t.test!("==")(page.value, ""[]);
968         t.test!("==")(page.type, Type.Empty);
969 
970         t.test!("==")(uid.value, ""[]);
971         t.test!("==")(uid.type, Type.Empty);
972 
973         t.test!("==")(not.type, Type.Empty);
974         t.test!("==")(not.value, ""[]);
975 
976         t.test!("==")(h.value, ""[]);
977         t.test!("==")(h.type, Type.Empty);
978 
979         t.test!("==")(w.value, ""[]);
980         t.test!("==")(w.type, Type.Empty);
981 
982         enum content2 = `{"imp":null}`;
983 
984         try
985         {
986             main.parse(content2);
987             t.test(false, "parse didn't throw"[]);
988         }
989         catch (JsonException e)
990         {
991             t.test!("==")(e.message(), "type mismatch"[]);
992         }
993 
994         bool fun (uint i, Type type, cstring value)
995         {
996             return false;
997         }
998 
999         scope imp2  = new GetArray(json, null, &fun, true),
1000               main2 = new Main(json, ["imp": imp2]);
1001 
1002         ok = main2.parse(content2);
1003 
1004         t.test(ok, "parse didn't return true"[]);
1005 
1006     }
1007 }