@@ -678,3 +678,326 @@ func TestIsJSONNull(t *testing.T) {
678
678
assert .False (t , isJSONNull ([]byte (`"null"` )))
679
679
assert .False (t , isJSONNull (nil ))
680
680
}
681
+
682
+ func rm (v any ) json.RawMessage {
683
+ b , _ := json .Marshal (v )
684
+ return json .RawMessage (b )
685
+ }
686
+
687
+ type covStringOpt struct {
688
+ N int `json:"n"`
689
+ SN int `json:"sn,string"` // triggers f.hasString branch
690
+ }
691
+
692
+ type covBadType struct {
693
+ Count int `json:"count"` // we will feed an object here to force field-level unmarshal error
694
+ }
695
+
696
+ func TestHitDecodeInto_Struct_SkipEmptyRaw (t * testing.T ) {
697
+ h := Hit {
698
+ "n" : json.RawMessage {}, // len(raw) == 0 => skip
699
+ "sn" : rm ("123" ), // ,string branch should set 123
700
+ }
701
+ var out covStringOpt
702
+ require .NoError (t , h .DecodeInto (& out ))
703
+ assert .Equal (t , 0 , out .N ) // untouched (skipped)
704
+ assert .Equal (t , 123 , out .SN ) // set via unmarshalSingleField
705
+ }
706
+
707
+ func TestHitDecodeInto_Struct_UnmarshalErrorOnField (t * testing.T ) {
708
+ h := Hit {
709
+ "count" : rm (map [string ]int {"oops" : 1 }), // object into int => error
710
+ }
711
+ var out covBadType
712
+ err := h .DecodeInto (& out )
713
+ require .Error (t , err )
714
+ assert .Contains (t , err .Error (), `decode field "count"` )
715
+ assert .Contains (t , err .Error (), "cannot unmarshal object" )
716
+ }
717
+
718
+ func TestHitDecodeInto_Map_StringKeyRequired (t * testing.T ) {
719
+ h := Hit {
720
+ "a" : rm (1 ),
721
+ }
722
+ var bad map [int ]int
723
+ err := h .DecodeInto (& bad )
724
+ require .Error (t , err )
725
+ assert .Contains (t , err .Error (), "map key must be string" )
726
+ }
727
+
728
+ func TestHitDecodeInto_Map_NullZeroAndValueErrors (t * testing.T ) {
729
+ // null → zero value of elem type.
730
+ // also force a value-unmarshal error on key "err".
731
+ h := Hit {
732
+ "ok" : rm (7 ),
733
+ "nil" : json .RawMessage ("null" ),
734
+ "err" : rm (map [string ]int {"x" : 1 }), // object into int => error
735
+ }
736
+
737
+ // First, map[string]any: null -> nil interface{}
738
+ var m1 map [string ]any
739
+ require .NoError (t , h .DecodeInto (& m1 ))
740
+ assert .Equal (t , float64 (7 ), m1 ["ok" ]) // numbers into interface{} become float64
741
+ assert .Nil (t , m1 ["nil" ])
742
+ // We didn't touch "err" yet because we returned early above; to test error path,
743
+ // try a typed map where we decode all keys and catch the error.
744
+
745
+ // Now, map[string]int: null -> 0; "err" should fail
746
+ var m2 map [string ]int
747
+ err := h .DecodeInto (& m2 )
748
+ require .Error (t , err )
749
+ assert .Contains (t , err .Error (), `decode map value for key "err"` )
750
+ assert .Contains (t , err .Error (), "cannot unmarshal object" )
751
+ }
752
+
753
+ func TestHitsDecodeInto_SliceOfMap_InterfaceHappy (t * testing.T ) {
754
+ h1 := Hit {"a" : rm (1 ), "b" : rm ("x" )}
755
+ h2 := Hit {"a" : rm (2 ), "b" : rm ("y" )}
756
+ hs := Hits {h1 , h2 }
757
+
758
+ var out []map [string ]any
759
+ require .NoError (t , hs .DecodeInto (& out ))
760
+ require .Len (t , out , 2 )
761
+ assert .Equal (t , float64 (1 ), out [0 ]["a" ])
762
+ assert .Equal (t , "x" , out [0 ]["b" ])
763
+ assert .Equal (t , float64 (2 ), out [1 ]["a" ])
764
+ assert .Equal (t , "y" , out [1 ]["b" ])
765
+ }
766
+
767
+ func TestHitsDecodeInto_SliceOfPtrMap_InterfaceHappy (t * testing.T ) {
768
+ h1 := Hit {"a" : rm (1 )}
769
+ h2 := Hit {"a" : rm (2 )}
770
+ hs := Hits {h1 , h2 }
771
+
772
+ var out []* map [string ]any
773
+ require .NoError (t , hs .DecodeInto (& out ))
774
+ require .Len (t , out , 2 )
775
+ require .NotNil (t , out [0 ])
776
+ require .NotNil (t , out [1 ])
777
+ assert .Equal (t , float64 (1 ), (* out [0 ])["a" ])
778
+ assert .Equal (t , float64 (2 ), (* out [1 ])["a" ])
779
+ }
780
+
781
+ type covElem struct {
782
+ V int `json:"v"`
783
+ }
784
+
785
+ func TestHitsDecodeInto_ErrorIndex_PropagatesForStruct (t * testing.T ) {
786
+ // First ok, second bad (object into int)
787
+ ok := Hit {"v" : rm (1 )}
788
+ bad := Hit {"v" : rm (map [string ]int {"x" : 1 })}
789
+ hs := Hits {ok , bad }
790
+
791
+ var out []covElem
792
+ err := hs .DecodeInto (& out )
793
+ require .Error (t , err )
794
+ assert .Contains (t , err .Error (), "decode hits[1]" ) // index included
795
+ assert .Contains (t , err .Error (), "decode field \" v\" " )
796
+ }
797
+
798
+ func TestHitsDecodeInto_ErrorIndex_PropagatesForMap (t * testing.T ) {
799
+ // First ok, second bad for typed map[string]int
800
+ ok := Hit {"k" : rm (5 )}
801
+ bad := Hit {"k" : rm (map [string ]int {"x" : 1 })}
802
+ hs := Hits {ok , bad }
803
+
804
+ var out []map [string ]int
805
+ err := hs .DecodeInto (& out )
806
+ require .Error (t , err )
807
+ assert .Contains (t , err .Error (), "decode hits[1]" )
808
+ assert .Contains (t , err .Error (), `decode map value for key "k"` )
809
+ }
810
+
811
+ func TestHitsDecodeInto_SliceElemPtrMap_NonStringKeyError (t * testing.T ) {
812
+ hs := Hits {Hit {"k" : rm (1 )}}
813
+ var out []* map [int ]int
814
+ err := hs .DecodeInto (& out )
815
+ require .Error (t , err )
816
+ assert .Contains (t , err .Error (), "slice element must be map with string key" )
817
+ }
818
+
819
+ func TestHitsDecodeInto_SliceElemMap_NonStringKeyError (t * testing.T ) {
820
+ hs := Hits {Hit {"k" : rm (1 )}}
821
+ var out []map [int ]int
822
+ err := hs .DecodeInto (& out )
823
+ require .Error (t , err )
824
+ assert .Contains (t , err .Error (), "slice element must be map with string key" )
825
+ }
826
+
827
+ func TestHitsDecodeInto_SliceElemPtrStruct_Happy (t * testing.T ) {
828
+ hs := Hits {
829
+ {"v" : rm (10 )},
830
+ {"v" : rm (20 )},
831
+ }
832
+ var out []* covElem
833
+ require .NoError (t , hs .DecodeInto (& out ))
834
+ require .Len (t , out , 2 )
835
+ assert .Equal (t , 10 , out [0 ].V )
836
+ assert .Equal (t , 20 , out [1 ].V )
837
+ }
838
+
839
+ func TestHitsDecodeInto_SliceElemStruct_Happy (t * testing.T ) {
840
+ hs := Hits {
841
+ {"v" : rm (3 )},
842
+ {"v" : rm (4 )},
843
+ }
844
+ var out []covElem
845
+ require .NoError (t , hs .DecodeInto (& out ))
846
+ require .Len (t , out , 2 )
847
+ assert .Equal (t , 3 , out [0 ].V )
848
+ assert .Equal (t , 4 , out [1 ].V )
849
+ }
850
+
851
+ func TestHitDecodeInto_Struct_StringTagBranch (t * testing.T ) {
852
+ h := Hit {"sn" : rm ("42" )}
853
+ var out covStringOpt
854
+ require .NoError (t , h .DecodeInto (& out ))
855
+ assert .Equal (t , 42 , out .SN )
856
+ }
857
+
858
+ func TestHitDecodeInto_Map_NullToZero_TypedInt (t * testing.T ) {
859
+ h := Hit {
860
+ "x" : rm (9 ),
861
+ "z" : json .RawMessage ("null" ),
862
+ }
863
+ var m map [string ]int
864
+ require .NoError (t , h .DecodeInto (& m ))
865
+ assert .Equal (t , 9 , m ["x" ])
866
+ assert .Equal (t , 0 , m ["z" ]) // zero value for int
867
+ }
868
+
869
+ func TestHitDecodeInto_DispatchError (t * testing.T ) {
870
+ var s string
871
+ err := Hit {"x" : rm (1 )}.DecodeInto (& s )
872
+ require .Error (t , err )
873
+ assert .Contains (t , err .Error (), "struct or map" )
874
+ }
875
+
876
+ func TestHitDecodeInto_EmptyRawUnknownKey_NoCrash (t * testing.T ) {
877
+ type S struct {
878
+ A int `json:"a"`
879
+ }
880
+ h := Hit {
881
+ "zzz" : json.RawMessage {}, // unknown + empty
882
+ "a" : rm (5 ),
883
+ }
884
+ var s S
885
+ require .NoError (t , h .DecodeInto (& s ))
886
+ assert .Equal (t , 5 , s .A )
887
+ }
888
+
889
+ func TestHitDecodeInto_Map_InterfaceTypes (t * testing.T ) {
890
+ h := Hit {
891
+ "n" : rm (1 ),
892
+ "s" : rm ("str" ),
893
+ "o" : rm (map [string ]any {"k" : 2 }),
894
+ "a" : rm ([]any {1 , "x" }),
895
+ }
896
+ var m map [string ]any
897
+ require .NoError (t , h .DecodeInto (& m ))
898
+ assert .Equal (t , float64 (1 ), m ["n" ])
899
+ assert .Equal (t , "str" , m ["s" ])
900
+ assert .Equal (t , map [string ]any {"k" : float64 (2 )}, m ["o" ])
901
+ assert .Equal (t , []any {float64 (1 ), "x" }, m ["a" ])
902
+
903
+ // Double-check types match what encoding/json would produce
904
+ b , _ := json .Marshal (h ) // {"n":1,"s":"str","o":{"k":2},"a":[1,"x"]}
905
+ var std map [string ]any
906
+ require .NoError (t , json .Unmarshal (b , & std ))
907
+ assert .Equal (t , std , m )
908
+ }
909
+
910
+ func TestHitDecodeInto_Map_PreserveAndOverwrite (t * testing.T ) {
911
+ h := Hit {
912
+ "a" : rm (10 ),
913
+ "b" : rm (20 ),
914
+ }
915
+ m := map [string ]int {"a" : 1 } // pre-populated
916
+ require .NoError (t , h .DecodeInto (& m ))
917
+ // "a" should be overwritten, "b" added
918
+ assert .Equal (t , 10 , m ["a" ])
919
+ assert .Equal (t , 20 , m ["b" ])
920
+ }
921
+
922
+ func TestHitsDecodeInto_SliceElemInvalidKind (t * testing.T ) {
923
+ hs := Hits {{"x" : rm (1 )}}
924
+ var out []int
925
+ err := hs .DecodeInto (& out )
926
+ require .Error (t , err )
927
+ assert .Contains (t , err .Error (), "slice element must be struct, *struct, map[string]T, or *map[string]T" )
928
+ }
929
+
930
+ func TestHitsDecodeInto_NonSlicePointer (t * testing.T ) {
931
+ hs := Hits {}
932
+ var x int
933
+ err := hs .DecodeInto (& x )
934
+ require .Error (t , err )
935
+ assert .Contains (t , err .Error (), "must point to a slice" )
936
+ }
937
+
938
+ func TestHitsDecodeInto_BadPtr (t * testing.T ) {
939
+ var hs Hits
940
+ err := hs .DecodeInto (nil )
941
+ require .Error (t , err )
942
+
943
+ var notPtr []map [string ]any
944
+ err = hs .DecodeInto (notPtr )
945
+ require .Error (t , err )
946
+ assert .Contains (t , err .Error (), "non-nil pointer" )
947
+ }
948
+
949
+ func TestHitDecodeInto_BadKinds (t * testing.T ) {
950
+ h := Hit {}
951
+ var ch chan int
952
+ err := h .DecodeInto (& ch )
953
+ require .Error (t , err )
954
+ assert .Contains (t , err .Error (), "struct or map" )
955
+
956
+ // Not pointer
957
+ var s covStringOpt
958
+ err = h .DecodeInto (s )
959
+ require .Error (t , err )
960
+ assert .Contains (t , err .Error (), "non-nil pointer" )
961
+ }
962
+
963
+ func TestHitDecodeInto_Map_CreatesMapIfNil (t * testing.T ) {
964
+ h := Hit {"x" : rm (1 )}
965
+ var m map [string ]any // nil
966
+ require .NoError (t , h .DecodeInto (& m ))
967
+ require .NotNil (t , m )
968
+ assert .Equal (t , float64 (1 ), m ["x" ])
969
+ }
970
+
971
+ func TestHitsDecodeInto_PtrMap_AllocAndFill (t * testing.T ) {
972
+ hs := Hits {
973
+ {"x" : rm (1 )},
974
+ {"y" : rm (2 )},
975
+ }
976
+ var out []* map [string ]any
977
+ require .NoError (t , hs .DecodeInto (& out ))
978
+ require .Len (t , out , 2 )
979
+ assert .Equal (t , float64 (1 ), (* out [0 ])["x" ])
980
+ assert .Equal (t , float64 (2 ), (* out [1 ])["y" ])
981
+ }
982
+
983
+ func TestHitDecodeInto_Struct_PointerEmbeddedAlloc (t * testing.T ) {
984
+ type Inner struct {
985
+ Z int `json:"z"`
986
+ }
987
+ type Wrap struct { * Inner }
988
+ h := Hit {"z" : rm (9 )}
989
+ var w Wrap
990
+ require .NoError (t , h .DecodeInto (& w ))
991
+ require .NotNil (t , w .Inner )
992
+ assert .Equal (t , 9 , w .Z )
993
+ }
994
+
995
+ func TestHitDecodeInto_Struct_UnknownKeysIgnored (t * testing.T ) {
996
+ type S struct {
997
+ A int `json:"a"`
998
+ }
999
+ h := Hit {"xxx" : rm (1 ), "a" : rm (2 )}
1000
+ var s S
1001
+ require .NoError (t , h .DecodeInto (& s ))
1002
+ assert .Equal (t , 2 , s .A )
1003
+ }
0 commit comments