Monday, April 1, 2013

Java-based JSON and JSON Schema User Interface (UI) generator

In a previous post I talked about using Metawidget on the client-side to generate UIs from JavaScript objects. I was asked if it could do the same thing in Java environments.

The answer is yes! The next release of Metawidget (3.3) will be able to inspect both JSON and JSON Schema files for useful metadata. Here's a sample using SWT:

public static void main( String[] args ) {

   // Metawidget

   Display display = new Display();
   Shell shell = new Shell( display );
   shell.setText( "JSON Viewer" );
   shell.setLayout( new FillLayout() );

   String json = "{ \"firstname\": \"Richard\", \"surname\": \"Kennard\", \"notes\": \"Software developer\" }";
   String jsonSchema = "{ properties: { \"firstname\": { \"required\": true }, \"notes\": { \"large\": true }}}";


   final SwtMetawidget metawidget = new SwtMetawidget( shell, SWT.None );
   metawidget.setInspector( new CompositeInspector( new CompositeInspectorConfig().setInspectors(
         new JsonInspector( new JsonInspectorConfig().setInputStream( new ByteArrayInputStream( json.getBytes() ) ) ),
         new JsonSchemaInspector( new JsonInspectorConfig().setInputStream( new ByteArrayInputStream( jsonSchema.getBytes() ) ) ) )
) );
   metawidget.setInspectionPath( "personObject" );

   // Shell

   shell.setVisible( true );
   shell.open();

   while ( !shell.isDisposed() ) {
      if ( !display.readAndDispatch() ) {
         display.sleep();
      }
   }

   display.dispose();
}

This will produce the screen below:

Binding the JSON values into the UI will depend on your particular requirements. Here's an example that binds into a Google GSON object:

public class JsonBindingProcessor
   implements AdvancedWidgetProcessor<Control, SwtMetawidget> {

   //
   // Public methods
   //

   public void onStartBuild( SwtMetawidget metawidget ) {

      getWrittenComponents( metawidget ).clear();
   }

   /**
    * Retrieve the values from the JSON and put them in the Controls.
    */

   public Control processWidget( Control control, String elementName, Map<String, String> attributes, SwtMetawidget metawidget ) {

      String attributeName = attributes.get( NAME );
      getWrittenComponents( metawidget ).put( attributeName, control );

      // Fetch the value...

      JsonObject jsonObject = metawidget.getToInspect();
      String value = jsonObject.get( attributeName ).getAsString();

      // ...and apply it to the component. For simplicity, we won't worry about converters

      String controlProperty = metawidget.getValueProperty( control );
      ClassUtils.setProperty( control, controlProperty, value );

      return control;
   }

   public void onEndBuild( SwtMetawidget metawidget ) {

      // Do nothing
   }

   /**
    * Store the values from the Controls back into the JSON.
    */

   public void save( SwtMetawidget metawidget ) {

      JsonObject jsonObject = metawidget.getToInspect();

      for ( Map.Entry<String, Control> entry : getWrittenComponents( metawidget ).entrySet() ) {

         Control control = entry.getValue();
         String controlProperty = metawidget.getValueProperty( control );
         Object value = ClassUtils.getProperty( control, controlProperty );

         jsonObject.addProperty( entry.getKey(), (String) value );
      }
   }

   //
   // Private methods
   //

   /**
    * During load-time we keep track of all the controls. At save-time we write them all back
    * again.
    */

   private Map<String, Control> getWrittenComponents( SwtMetawidget metawidget ) {

      @SuppressWarnings( "unchecked" )
      Map<String, Control> writtenComponents = (Map<String, Control>) metawidget.getData( JsonBindingProcessor.class.getName() );

      if ( writtenComponents == null ) {
         writtenComponents = CollectionUtils.newHashMap();
         metawidget.setData( JsonBindingProcessor.class.getName(), writtenComponents );
      }

      return writtenComponents;
   }
}

To add it into the example above:

metawidget.setInspectionPath( "personObject" );

metawidget.addWidgetProcessor( new JsonBindingProcessor() );
metawidget.setToInspect( new JsonParser().parse( json ) );

Button button = new Button( metawidget, SWT.NONE );
button.setText( "Save" );

button.addSelectionListener( new SelectionAdapter() {

   @Override
   public void widgetSelected( SelectionEvent e ) {

      metawidget.getWidgetProcessor( JsonBindingProcessor.class ).save( metawidget );
      System.out.println( metawidget.getToInspect().toString() );
   }
} );


// Shell

shell.setVisible( true );

Feedback welcome!

0 comments: