This is actually easy, so here is a complete example, along with the following details:
- For parsing JSON (or XML) SuperObject by Henri Gourvest does a good job, but no longer supports Delphi 7. Consider using a fork that might seem outdated but is more than enough for what you need. Making the original to support Delphi 7 is trivial, too.
- For decoding Base64 you can use Daniel Wischnewski's unit - if you think all the assembler code is too cryptic you'll see that writing your own Base64 decoder is no black magic either.
- I modified your original JSON data for its clearly corrupted picture data to now have 2 complete JPEG (actually JFIF) files, 1 corrupt file and 1 complete GIF file - this should demonstrate how solid the code is and where expectations are met and where they fail.
- The entire code is one DPR file - save it as such and (naturally) expect a console application. I won't display any of the
TImage
s, but looking up their width and height should be enough proof.
program JsonToImageArray;
{$APPTYPE CONSOLE}
uses
superobject, // https://github.com/frostney/superobject
Base64, // https://github.com/mwsrc/XtremeRAT/blob/master/Servidor/Passwords/Base64.pas
ExtCtrls, // TImage
Jpeg, // TJPEGImage
Classes; // TStringStream
const
CRLF= #13#10; // Windows linebreak
// Keep in mind that you'll most likely get your JSON data from elsewhere,
// so don't care if it looks cumbersome here as String constant.
sJsonInput
= '{'+ CRLF
+ ' "operator": "Capture",'+ CRLF
+ ' "info": {'+ CRLF
+ ' "DeviceID": 123456,'+ CRLF
+ ' "Listnum": 4,'+ CRLF
+ ' "List": ['+ CRLF
// A 1x1 JPEG, apparently with the lowest possible filesize.
// https://gist.github.com/scotthaleen/32f76a413e0dfd4b4d79c2a534d49c0b
+ ' {'+ CRLF
+ ' "LibSnapID": 1,'+ CRLF
+ ' "SnapPicinfo": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/'
+ '2wBDAP/////////////////////////////////////////////////////////////////////'
+ '/////////////////wgALCAABAAEBAREA/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABPx'
+ 'A="'+ CRLF
+ ' },'+ CRLF
// Illegal Base64 data and even if converting it partially the data would
// be way too little to hold a JPEG.
+ ' {'+ CRLF
+ ' "LibSnapID": 2,'+ CRLF
+ ' "SnapPicinfo": "data:image/jpeg;base64,Qk2K5wAAAAAAAD......"'+ CRLF
+ ' },'+ CRLF
// A 28x26 JPEG, custom-made.
+ ' {'+ CRLF
+ ' "LibSnapID": 3,'+ CRLF
+ ' "SnapPicinfo": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/'
+ '2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAw'
+ 'MBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA'
+ 'wMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAAaABwDASIAAhEBAxEB/8QAHwAAAQUBAQEBA'
+ 'QEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1Fh'
+ 'ByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1h'
+ 'ZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxM'
+ 'XGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAA'
+ 'AECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKR'
+ 'obHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2h'
+ 'panN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0t'
+ 'PU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD99tR1G30fT57u7nhtbW1ja'
+ 'aaaZwkcKKMszMeAAASSeABXkH7Mf/BQj4Mftn+OvGnh34U/EHQ/HuofD1rRdcl0fzLixt/tSu0H'
+ 'l3YX7PcBhFJkwSSBShDYPFev6jfppenz3MizNHbxtK4hheaQhRk7UQFmPHCqCSeACa/L3/gkrrm'
+ 'p6J/wV8/bC8Sat4C+MHh/w78adW0W58H6rrfw18QaXY6lHaWl39oaSe4s447XbuUD7S0RdmCpuJ'
+ 'Ap0PfqypvpByXqpRVvmnJ/9uvzCt7tD2q35or5Pd/L9T9SKKKKQBRRRQAUUUUAf//Z"'+ CRLF
+ ' },'+ CRLF
// A 1x1 GIF as per https://stackoverflow.com/a/13139830/4299358 but our
// code just doesn't expect other picture formats than JPEG.
+ ' {'+ CRLF
+ ' "LibSnapID": 2,'+ CRLF
+ ' "SnapPicinfo": "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5B'
+ 'AEAAAAALAAAAAABAAEAAAICRAEAOw=="'+ CRLF
+ ' },'+ CRLF
+ ' ]'+ CRLF
+ ' }'+ CRLF
+ '}';
type
TArrayImage= Array of TImage; // So it can be used as parameter
// Actually turning the JSON into TImage instances
function parse
( const sInput: WideString // JSON String, i.e. '{ "foo": 42 }'
; out aImg: TArrayImage // All the pictures that we want
): Integer; // Error reporting: 0 means no error occured
var
oRoot, oInfo, oList: ISuperObject; // Walking the data tree from node to node
vList: TSuperArray; // The list of pictures is an array, not an object
iList, iStart: Integer; // List items and String matches
sSnapPicinfo, sBase64, sBinary: AnsiString;
oStr: TStringStream; // For feeding the JPEG instance with data
oJpg: TJPEGImage; // The picture we want
begin
result:= 0; // Everything okay so far
oRoot:= TSuperObject.ParseString( PWideChar(sInput), TRUE );
if oRoot= nil then begin
result:= 1; // JSON parsing failed
exit;
end;
// The whole JSON is one object and we want this member of it
oInfo:= oRoot.AsObject.O['info'];
if oInfo= nil then begin
result:= 2; // Element "info" not found
exit;
end;
// The whole "info" is one object again and we want this member
oList:= oInfo.AsObject.O['List'];
if oList= nil then begin
result:= 3; // Element "List" not found
exit;
end;
vList:= oList.AsArray; // Should be an array
if vList= nil then begin
result:= 4; // List is no array
exit;
end;
// Coming to this position means we successfully walked thru the JSON to at least say
// how big the array of pictures will be.
SetLength( aImg, vList.Length );
for iList:= 0 to vList.Length- 1 do begin
aImg[iList]:= nil; // No image yet, many chances of failure ahead
// Each element of the array is an object again and we want one specific member of it
sSnapPicinfo:= vList.O[iList].AsObject.S['SnapPicinfo'];
// Making sure we can deal with the picture format
iStart:= Pos( 'data:image/jpeg;', sSnapPicinfo );
if iStart<> 1 then begin
result:= 5; // Unsupported picture format
continue;
end;
// Making sure the data really is Base64 encoded
iStart:= Pos( 'base64,', sSnapPicinfo );
if iStart= 0 then begin
result:= 6; // Unexpected data format
continue;
end;
// All the rest of the text is Base64 data that we'll decode. Should there be an
// error during the conversion (illegal character, unexpected end...) then the
// result will always be empty.
sBase64:= Copy( sSnapPicinfo, iStart+ 7, Length( sSnapPicinfo ) );
sBinary:= uBase64.Base64Decode( sBase64 );
if Length( sBinary )= 0 then begin
result:= 7; // Base64 conversion failed
continue;
end;
// Now we have the binary of what seems to be a JPEG file
oStr:= TStringStream.Create( sBinary );
oJpg:= TJPEGImage.Create;
try
try
// This can fail for various reasons; JPEG comes with a couple of variants
// and Delphi 7 does not support all of them.
oJpg.LoadFromStream( oStr );
// Create instance in the output array
aImg[iList]:= TImage.Create( nil );
try
aImg[iList].AutoSize:= TRUE; // Otherwise .Height and .Width are no help
aImg[iList].Picture.Assign( oJpg ); // Again this might fail
except
result:= 9; // Assigning failed
end;
except
result:= 8; // Image reading failed
end;
finally
oJpg.Free;
end;
oStr.Free; // What should ever fail with a TStringStream?
end;
end;
var
aImg: TArrayImage; // The result we want to have
// Just print that array of images so we see what it actually contains
procedure PrintImages( sTitle: String );
var
iImg: Integer;
begin
Writeln( sTitle, ': ', Length( aImg ), ' images.' ); // How many elements?
for iImg:= Low( aImg ) to High( aImg ) do begin
Write( '- Image #', iImg+ 1, ': ' );
if aImg[iImg]<> nil then begin // We have an instance and look up the picture dimensions
Writeln( aImg[iImg].Width, 'x', aImg[iImg].Height );
end else Writeln( 'empty' ); // No instance
end;
end;
begin
case parse( sJsonInput, aImg ) of // Handing over the JSON as input and the array as output
1.. 4: Writeln( 'Failure' );
5.. 9: PrintImages( 'Partial success' ); // Only 2 out of 4 should be read successfully
0: PrintImages( 'Full success' );
end;
end.