def from_json(cls, text):
        """Return a new workflow, deserialized from a JSON string"""

        d = json.loads(text)

        meta = d.get('meta', {})
        workflow = cls(name=meta.get('name'), description=meta.get('description'))

        for workflow_input in d.get('inputs', []):
            parameter_cls = Parameter.by_id(workflow_input['type'])

            args = [workflow_input['name']]
            kwargs = {'required': True}
            if workflow_input.get('args'):
                args = workflow_input['args'][0] + args
                kwargs.update(workflow_input['args'][1])
                args, kwargs = parameter_cls.deserialize_args(args, kwargs)

            workflow.inputs.append(parameter_cls(*args, **kwargs))

        for node in d.get('workflow', []):
            node_inputs = {}
            for k, v in six.iteritems(node.get('inputs', {})):
                node_inputs[k] = (v['source'], v.get('input') or v.get('node'))

            workflow.add_node(node['id'], Task.by_name(node['task'])(), node_inputs)

        for output in d.get('outputs', []):
            node = output['node']
            node_parameters = ParameterCollection(workflow.nodes_by_id[node[0]].task.outputs)

            # Add parameter to workflow output
            output_param = copy.copy(node_parameters.by_name[node[1]])
            output_param.name = output['name']
            workflow.outputs.append(output_param)

            workflow.map_output(node[0], node[1], output['name'])

        return workflow