Try this on for size then. I did it for a utility I needed yesterday. It uses SuperObject. I left all field types in the code in case you want to add other special treatments or tweak any of those I put in. It's working for me on many random datasets right now.
class procedure TTool.ExportDataSetToJson(DataSet: TDataSet; FileName: string; Append: boolean = false);const SData = 'data';var json : ISuperObject; item : ISuperObject; wasActive: boolean; fld : TField;begin json := SO; json.O[SData] := SA([]); wasActive := DataSet.Active; try DataSet.Active := true; DataSet.First; while not DataSet.Eof do begin item := SO; for fld in DataSet.Fields do begin case fld.DataType of// ftUnknown: ; ftString, ftBlob, ftMemo, ftFmtMemo, ftBytes, ftVarBytes, ftFixedChar, ftFixedWideChar, ftWideMemo, ftByte, ftWideString: item.S[fld.FieldName] := fld.AsString; ftBoolean: item.B[fld.FieldName] := fld.AsBoolean; ftFloat, ftSingle, ftExtended, ftCurrency, ftFMTBcd, ftBCD: item.D[fld.FieldName] := fld.AsFloat; ftTime : item.S[fld.FieldName] := TimeToJson(fld.AsDateTime); ftDate, ftTimeStamp, ftOraTimeStamp, ftDateTime: item.S[fld.FieldName] := DateTimeToJson(fld.AsDateTime); ftSmallint, ftInteger, ftWord, ftAutoInc, ftLongWord, ftShortint, ftLargeInt: item.I[fld.FieldName] := fld.AsLargeInt;// ftGraphic: ;// ftParadoxOle: ;// ftDBaseOle: ;// ftTypedBinary: ;// ftCursor: ;// ftADT: ;// ftArray: ;// ftReference: ;// ftDataSet: ;// ftOraBlob: ;// ftOraClob: ;// ftVariant: ;// ftInterface: ;// ftIDispatch: ; ftGuid: item.S[fld.FieldName] := fld.AsString;// ftOraInterval: ;// ftConnection: ;// ftParams: ;// ftStream: ;// ftTimeStampOffset: ;// ftObject: ; else item.S[fld.FieldName] := fld.AsString; end; end; DataSet.Next; json.A[SData].Add(item); end; if Append then TFile.AppendAllText(FileName, json.AsJSon(true, true)) else json.SaveTo(FileName, true, true); finally DataSet.Active := wasActive; end;end;