JsonType.java
/*
*
* The DbUnit Database Testing Framework
* Copyright (C)2002-2026, DbUnit.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package org.dbunit.ext.postgresql;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Objects;
import org.dbunit.dataset.datatype.AbstractDataType;
import org.dbunit.dataset.datatype.TypeCastException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Adapter to handle conversion between PostgreSQL native {@code json}/
* {@code jsonb} types and Strings.
*
* <p>PostgreSQL requires a bound parameter's declared type to match its
* target {@code json}/{@code jsonb} column exactly - it has no implicit cast
* between the two - so one instance handles only the single sql type name it
* was constructed with. {@link PostgresqlDataTypeFactory} constructs the
* matching instance for each column automatically; there is normally no need
* to instantiate this class directly.
*
* <p>This class only shuttles the column's raw text between the driver and
* the dataset; it does not compare JSON values semantically. Use
* {@link org.dbunit.assertion.comparer.value.IsActualEqualToExpectedJsonValueComparer}
* to compare json/jsonb column values by their parsed document structure
* instead of raw text.
*
* @author Jeff Jensen
* @since 3.5.0
*/
public class JsonType extends AbstractDataType
{
private static final Logger logger =
LoggerFactory.getLogger(JsonType.class);
private final String sqlTypeName;
/**
* Creates a data type adapter for the given PostgreSQL JSON sql type
* name.
*
* @param sqlTypeName
* The sql type name, {@code "json"} or {@code "jsonb"},
* needed to invoke the {@code setType()} method on the
* PGobject class.
*/
public JsonType(final String sqlTypeName)
{
super(Objects.requireNonNull(sqlTypeName,
"The parameter 'sqlTypeName' must not be null"), Types.OTHER,
String.class, false);
this.sqlTypeName = sqlTypeName;
}
@Override
public Object getSqlValue(final int column, final ResultSet resultSet)
throws SQLException, TypeCastException
{
return resultSet.getString(column);
}
@Override
public void setSqlValue(final Object value, final int column,
final PreparedStatement statement)
throws SQLException, TypeCastException
{
if (value == null)
{
statement.setNull(column, Types.OTHER);
return;
}
statement.setObject(column,
getJson(value, statement.getConnection()));
}
@Override
public Object typeCast(final Object value) throws TypeCastException
{
return value == null ? null : value.toString();
}
/**
* Returns the sql type name this instance handles.
*
* @return {@code "json"} or {@code "jsonb"}, whichever sql type name this
* instance was constructed with.
*/
public String getSqlTypeName()
{
return sqlTypeName;
}
private Object getJson(final Object value, final Connection connection)
throws TypeCastException
{
logger.debug("getJson(value={}, connection={}) - start", value,
connection);
Object tempJson = null;
try
{
final Class<?> aPGObjectClass = super.loadClass(
"org.postgresql.util.PGobject", connection);
final Constructor<?> ct = aPGObjectClass.getConstructor();
tempJson = ct.newInstance();
final Method setTypeMethod =
aPGObjectClass.getMethod("setType", String.class);
setTypeMethod.invoke(tempJson, sqlTypeName);
final Method setValueMethod =
aPGObjectClass.getMethod("setValue", String.class);
setValueMethod.invoke(tempJson, value.toString());
} catch (final ReflectiveOperationException e)
{
throw new TypeCastException(value, this, e);
}
return tempJson;
}
}