function init( params ) {
    // extract parameters for convenience
    let { log, config } = params;
    let jsonConfig = config.jsonCodec;
    if( ! jsonConfig ) {
        log.warn( 'Add jsonCodec object to configuration' );
    }

    let readJPath = function( prop ) {
        if( jsonConfig && jsonConfig.properties ) {
            return jsonConfig.properties[ prop ];
        }
    };

    let emptyMessage = function( topic ) {
        if( jsonConfig ) {
            if( jsonConfig.fixedByTopic && jsonConfig.fixedByTopic[ topic ] ) {
                return JSON.parse( JSON.stringify( jsonConfig.fixedByTopic[ topic ] ) );
            } else if( jsonConfig.fixed ) {
                return JSON.parse( JSON.stringify( jsonConfig.fixed ) );
            }
        }
        return {};
    };

    // pending messages/timers by MQTT topic
    let pending = {};

    // get message object which will be published (automatically)
    let publishMessage = function( topic, publish ) {
        let entry = pending[ topic ];
        if( entry ) {
            // existing entry - clear any timer
            if( entry.tmr ) {
                clearTimeout( entry.tmr );
            }
        } else {
            // new entry
            entry = pending[ topic ] = { msg: emptyMessage( topic ) };
        }

        // publish later
        entry.tmr = setTimeout( () => {
            if( jsonConfig && jsonConfig.retain ) {
                // retain: just clear timer - keep the message
                entry.tmr = null;
            } else {
                // no retain: remove entry
                pending[ topic ] = null;
            }
            publish( JSON.stringify( entry.msg ) );
        }, 50 );

        return entry.msg;
    }

    /**
     * Encode message before sending.
     * The output function may be called to deliver an encoded value for the property later.
     * @param {string} message Message from mqttthing to be published to MQTT
     * @param {object} info Object giving contextual information
     * @param {string} info.topic MQTT topic to be published
     * @param {string} info.property Property associated with publishing operation
     * @param {function} output Function which may be called to deliver the encoded value asynchronously
     * @returns {string} Processed message (optionally)
     */
    function encode( message, info, output ) { // eslint-disable-line no-unused-vars
        let diag = ! jsonConfig || jsonConfig.diag;
        let jpath = readJPath( info.property );
        if( jpath ) {
            let msg = publishMessage( info.topic, output );
            setJson( msg, jpath, message );
        } else {
            diag = true;
        }
        if( diag ) {
            log( `json-codec: encode() called for topic [${info.topic}], property [${info.property}] with message [${message}]` );
        }
    }

    /**
     * Decode received message, and optionally return decoded value.
     * The output function may be called to deliver a decoded value for the property later.
     * @param {string} message Message received from MQTT
     * @param {object} info Object giving contextual information
     * @param {string} info.topic MQTT topic received
     * @param {string} info.property Property associated with subscription
     * @param {function} output Function which may be called to deliver the decoded value asynchronously
     * @returns {string} Processed message (optionally)
     */
    function decode( message, info, output ) { // eslint-disable-line no-unused-vars
        let diag = ! jsonConfig || jsonConfig.diag;
        let jpath = readJPath( info.property );
        let decoded;
        if( jpath ) {
            let msg = JSON.parse( message );
            decoded = getJson( msg, jpath );
        } else {
            diag = true;
        }
        if( diag ) {
            log( `json-codec: decode() called for topic [${info.topic}], property [${info.property}] with message [${message}]` );
        }
        return decoded;
    }

    // return encode and decode functions
    return { 
        encode, 
        decode 
    };
}
Initialise codec for accessory
@param {object} params Initialisation parameters object
@param {function} params.log Logging function
@param {object} params.config Configuration
@param {function} params.publish Function to publish a message directly to MQTT
@param {function} params.notify Function to send MQTT-Thing a property notification
@return {object} Encode and/or decode functions