1

I have a Json like this:

{  
  "operator": "Capture", 
  "info": {
    "DeviceID": 123456,
    "Listnum": 4,
    "List": [
      {
        "LibSnapID": 1,
        "SnapPicinfo": "data:image/jpeg;base64,Qk2K5wAAAAAAAD......"
      },
      {
        "LibSnapID": 2,
        "SnapPicinfo": "data:image/jpeg;base64,Qk225QAAAAAAADYAAAAoAAAAiAAAAJAAAAA......"
      },
      {
        "LibSnapID": 3,
        "SnapPicinfo": "data:image/jpeg;base64,Qk2K5wAAAAAAADYAAAAoAAAAjAAAAI0AA...."
      },
      {
        "LibSnapID": 4,
        "SnapPicinfo": "data:image/jpeg;base64,Qk3m5QAAAAAAADYAAAAoAAAAjAAAAIwAAAAB.."
      },
    ]
  }
}

I am new to Json, and would apreciate some help in getting the SnapPicinfo into a TImage array.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 1
    Delphi 7 has no built-in libraries for JSON or Base64 (modern versions do), so you will have to either use 3rd party libraries (plenty of them are available for download), or else write your own parsers (JSON and Base64 are not very hard to parse manually). – Remy Lebeau Jul 06 '21 at 00:58
  • Once you pick a JSON library you can read its documentation and then it will be routine to extract the strings. Then you can use a Base64 library to convert to a jpeg byte stream which you can load into a jpeg image which in turn you can assign to TImage control. – David Heffernan Jul 06 '21 at 07:54

1 Answers1

1

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 TImages, 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.
AmigoJack
  • 5,234
  • 1
  • 15
  • 31