[ogr] fix JSON array interpreted as QVariantMap

Fix #61728
This commit is contained in:
Alessandro Pasotti 2025-05-20 19:16:31 +02:00
parent 78aa6f1fc4
commit 107f3169bf
3 changed files with 199 additions and 3 deletions

View File

@ -886,9 +886,9 @@ void QgsOgrProvider::loadFields()
QMutexLocker locker( datasetMutex );
#endif
for ( int i = 0; i < fdef.GetFieldCount(); ++i )
for ( int fieldIndex = 0; fieldIndex < fdef.GetFieldCount(); ++fieldIndex )
{
OGRFieldDefnH fldDef = fdef.GetFieldDefn( i );
OGRFieldDefnH fldDef = fdef.GetFieldDefn( fieldIndex );
const OGRFieldType ogrType = OGR_Fld_GetType( fldDef );
const OGRFieldSubType ogrSubType = OGR_Fld_GetSubType( fldDef );
@ -896,6 +896,38 @@ void QgsOgrProvider::loadFields()
QMetaType::Type varSubType = QMetaType::Type::UnknownType;
QgsOgrUtils::ogrFieldTypeToQVariantType( ogrType, ogrSubType, varType, varSubType );
// Handle special case for OGRFieldType::OFSTJSON which is not always a map.
// If subtype is JSON and varType is map, try to load a feature and check if it's
// really an object (rather than something else like an array)
// fallback to string given that only JSON object (map) is supported.
if ( varType == QMetaType::Type::QVariantMap && ogrSubType == OFSTJSON )
{
QRecursiveMutex *layerMutex = nullptr;
OGRLayerH ogrLayer = mOgrLayer->getHandleAndMutex( layerMutex );
QMutexLocker layerLocker( layerMutex );
gdal::ogr_feature_unique_ptr f( OGR_L_GetNextFeature( ogrLayer ) );
if ( f )
{
const char *json = OGR_F_GetFieldAsString( f.get(), fieldIndex );
if ( json && json[0] != '\0' )
{
try
{
const auto json_element = json::parse( json );
if ( ! json_element.is_object() )
{
varType = QMetaType::Type::QString;
}
}
catch ( const json::parse_error & )
{
varType = QMetaType::Type::QString;
}
}
OGR_L_ResetReading( ogrLayer );
}
}
//TODO: fix this hack
#ifdef ANDROID
QString name = OGR_Fld_GetNameRef( fldDef );

View File

@ -1862,7 +1862,6 @@ void QgsOgrUtils::ogrFieldTypeToQVariantType( OGRFieldType ogrType, OGRFieldSubT
case OFTWideString:
if ( ogrSubType == OFSTJSON )
{
ogrSubType = OFSTJSON;
variantType = QMetaType::Type::QVariantMap;
variantSubType = QMetaType::Type::QString;
}

View File

@ -58,6 +58,8 @@ class TestQgsOgrProvider : public QgsTest
void testExtent();
void testVsiCredentialOptions();
void testVsiCredentialOptionsQuerySublayers();
void testJSONFields_data();
void testJSONFields();
private:
QString mTestDataDir;
@ -579,5 +581,168 @@ void TestQgsOgrProvider::testVsiCredentialOptionsQuerySublayers()
}
void TestQgsOgrProvider::testJSONFields_data()
{
QTest::addColumn<QString>( "jsonData" );
QTest::addColumn<int>( "expectedType" );
QTest::newRow( "array of map" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"array_of_map": [
{
"a": 1,
"b": 2.0
}
]
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::QString );
QTest::newRow( "map" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"map": {
"a": 1,
"b": 2.0
}
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::QVariantMap );
QTest::newRow( "int" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"int": 1
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::Int );
QTest::newRow( "stringlist" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"string_list": [ "a", "b", "c" ]
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::QStringList );
QTest::newRow( "string" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"string": "a"
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::QString );
QTest::newRow( "double" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"double": 1.0
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::Double );
QTest::newRow( "bool" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"bool": true
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::Bool );
QTest::newRow( "int list" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"int_list": [1, 2, 3]
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::QVariantList );
QTest::newRow( "real list" ) << QStringLiteral( R"json(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"real_list": [1.0, 2.1, 3]
}
}
]
}
)json" ) << static_cast<int>( QMetaType::Type::QVariantList );
}
void TestQgsOgrProvider::testJSONFields()
{
QFETCH( QString, jsonData );
QFETCH( int, expectedType );
QTemporaryDir dir;
QString filePath = dir.path() + "/test.json";
QFile file( filePath );
if ( file.open( QIODevice::WriteOnly ) )
{
QTextStream textStream( &file );
textStream << jsonData;
file.close();
}
QgsVectorLayer layer { filePath, QStringLiteral( "json" ), QLatin1String( "ogr" ) };
QVERIFY( layer.isValid() );
QgsFields fields = layer.fields();
QCOMPARE( fields.count(), 1 );
QgsField field = fields.at( 0 );
QCOMPARE( static_cast<int>( field.type() ), expectedType );
}
QGSTEST_MAIN( TestQgsOgrProvider )
#include "testqgsogrprovider.moc"