Skip to content

Commit

Permalink
Use field formatter when exporting feature attributes to JSON
Browse files Browse the repository at this point in the history
This means that field values which utilise widget setups like
value maps will correctly show the "friendly" value
for the field, instead of the raw values.
  • Loading branch information
nyalldawson committed May 24, 2017
1 parent 987f80a commit a5e3f19
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 9 deletions.
11 changes: 8 additions & 3 deletions python/core/qgsjsonutils.sip
Expand Up @@ -25,7 +25,7 @@ class QgsJSONExporter
%End
public:

QgsJSONExporter( const QgsVectorLayer *vectorLayer = 0, int precision = 6 );
QgsJSONExporter( QgsVectorLayer *vectorLayer = 0, int precision = 6 );
%Docstring
Constructor for QgsJSONExporter.
\param vectorLayer associated vector layer (required for related attribute export)
Expand Down Expand Up @@ -94,7 +94,7 @@ class QgsJSONExporter
:rtype: bool
%End

void setVectorLayer( const QgsVectorLayer *vectorLayer );
void setVectorLayer( QgsVectorLayer *vectorLayer );
%Docstring
Sets the associated vector layer (required for related attribute export). This will automatically
update the sourceCrs() to match.
Expand Down Expand Up @@ -247,10 +247,15 @@ class QgsJSONUtils
:rtype: str
%End

static QString exportAttributes( const QgsFeature &feature );
static QString exportAttributes( const QgsFeature &feature, QgsVectorLayer *layer = 0,
QVector<QVariant> attributeWidgetCaches = QVector<QVariant>() );
%Docstring
Exports all attributes from a QgsFeature as a JSON map type.
\param feature feature to export
\param layer optional associated vector layer. If specified, this allows
richer export utilising settings like the layer's fields widget configuration.
\param attributeWidgetCaches optional widget configuration cache. Can be used
to speed up exporting the attributes for multiple features from the same layer.
:rtype: str
%End

Expand Down
35 changes: 31 additions & 4 deletions src/core/qgsjsonutils.cpp
Expand Up @@ -23,11 +23,13 @@
#include "qgsproject.h"
#include "qgscsexception.h"
#include "qgslogger.h"
#include "qgsfieldformatterregistry.h"
#include "qgsfieldformatter.h"

#include <QJsonDocument>
#include <QJsonArray>

QgsJSONExporter::QgsJSONExporter(QgsVectorLayer *vectorLayer, int precision )
QgsJSONExporter::QgsJSONExporter( QgsVectorLayer *vectorLayer, int precision )
: mPrecision( precision )
, mIncludeGeometry( true )
, mIncludeAttributes( true )
Expand Down Expand Up @@ -119,7 +121,7 @@ QString QgsJSONExporter::exportFeature( const QgsFeature &feature, const QVarian

if ( mIncludeAttributes )
{
QgsFields fields = feature.fields();
QgsFields fields = mLayer.data() ? mLayer->fields() : feature.fields();

for ( int i = 0; i < fields.count(); ++i )
{
Expand All @@ -130,6 +132,14 @@ QString QgsJSONExporter::exportFeature( const QgsFeature &feature, const QVarian
properties += QLatin1String( ",\n" );
QVariant val = feature.attributes().at( i );

if ( mLayer.data() )
{
QgsEditorWidgetSetup setup = fields.at( i ).editorWidgetSetup();
QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
if ( fieldFormatter != QgsApplication::fieldFormatterRegistry()->fallbackFieldFormatter() )
val = fieldFormatter->representValue( mLayer.data(), i, setup.config(), QVariant(), val );
}

properties += QStringLiteral( " \"%1\":%2" ).arg( fields.at( i ).name(), QgsJSONUtils::encodeValue( val ) );

++attributeCounter;
Expand Down Expand Up @@ -166,14 +176,22 @@ QString QgsJSONExporter::exportFeature( const QgsFeature &feature, const QVarian
if ( childLayer )
{
QgsFeatureIterator it = childLayer->getFeatures( req );
QVector<QVariant> attributeWidgetCaches;
for ( int fieldIndex = 0; fieldIndex < childLayer->fields().count(); ++fieldIndex )
{
QgsEditorWidgetSetup setup = childLayer->fields().at( fieldIndex ).editorWidgetSetup();
QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
attributeWidgetCaches.append( fieldFormatter->createCache( childLayer, fieldIndex, setup.config() ) );
}

QgsFeature relatedFet;
int relationFeatures = 0;
while ( it.nextFeature( relatedFet ) )
{
if ( relationFeatures > 0 )
relatedFeatureAttributes += QLatin1String( ",\n" );

relatedFeatureAttributes += QgsJSONUtils::exportAttributes( relatedFet );
relatedFeatureAttributes += QgsJSONUtils::exportAttributes( relatedFet, childLayer, attributeWidgetCaches );
relationFeatures++;
}
}
Expand Down Expand Up @@ -267,7 +285,7 @@ QString QgsJSONUtils::encodeValue( const QVariant &value )
}
}

QString QgsJSONUtils::exportAttributes( const QgsFeature &feature )
QString QgsJSONUtils::exportAttributes( const QgsFeature &feature, QgsVectorLayer *layer, QVector<QVariant> attributeWidgetCaches )
{
QgsFields fields = feature.fields();
QString attrs;
Expand All @@ -277,6 +295,15 @@ QString QgsJSONUtils::exportAttributes( const QgsFeature &feature )
attrs += QLatin1String( ",\n" );

QVariant val = feature.attributes().at( i );

if ( layer )
{
QgsEditorWidgetSetup setup = layer->fields().at( i ).editorWidgetSetup();
QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
if ( fieldFormatter != QgsApplication::fieldFormatterRegistry()->fallbackFieldFormatter() )
val = fieldFormatter->representValue( layer, i, setup.config(), attributeWidgetCaches.count() >= i ? attributeWidgetCaches.at( i ) : QVariant(), val );
}

attrs += encodeValue( fields.at( i ).name() ) + ':' + encodeValue( val );
}
return attrs.prepend( '{' ).append( '}' );
Expand Down
7 changes: 6 additions & 1 deletion src/core/qgsjsonutils.h
Expand Up @@ -240,8 +240,13 @@ class CORE_EXPORT QgsJSONUtils

/** Exports all attributes from a QgsFeature as a JSON map type.
* \param feature feature to export
* \param layer optional associated vector layer. If specified, this allows
* richer export utilising settings like the layer's fields widget configuration.
* \param attributeWidgetCaches optional widget configuration cache. Can be used
* to speed up exporting the attributes for multiple features from the same layer.
*/
static QString exportAttributes( const QgsFeature &feature );
static QString exportAttributes( const QgsFeature &feature, QgsVectorLayer *layer = nullptr,
QVector<QVariant> attributeWidgetCaches = QVector<QVariant>() );

/** Parse a simple array (depth=1).
* \param json the JSON to parse
Expand Down
70 changes: 69 additions & 1 deletion tests/src/python/test_qgsjsonutils.py
Expand Up @@ -28,7 +28,8 @@
QgsLineString,
NULL,
QgsVectorLayer,
QgsRelation
QgsRelation,
QgsEditorWidgetSetup
)
from qgis.PyQt.QtCore import QVariant, QTextCodec

Expand Down Expand Up @@ -145,6 +146,20 @@ def testExportAttributes(self):
"population":198}"""
self.assertEqual(QgsJSONUtils.exportAttributes(feature), expected)

# test using field formatters
source = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer",
"parent", "memory")
pf1 = QgsFeature()
pf1.setFields(source.fields())
pf1.setAttributes(["test1", 1])

setup = QgsEditorWidgetSetup('ValueMap', {"map": {"one": 1, "two": 2, "three": 3}})
source.setEditorWidgetSetup(1, setup)

expected = """{"fldtxt":"test1",
"fldint":"one"}"""
self.assertEqual(QgsJSONUtils.exportAttributes(pf1, source), expected)

def testJSONExporter(self):
""" test converting features to GeoJSON """
fields = QgsFields()
Expand Down Expand Up @@ -392,6 +407,38 @@ def testJSONExporter(self):
self.assertTrue(exp_f == expected or exp_f == expected2)
exporter.setIncludeGeometry(True)

def testExportFeatureFieldFormatter(self):
""" Test exporting a feature with formatting fields """

# source layer
source = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer",
"parent", "memory")
pr = source.dataProvider()
pf1 = QgsFeature()
pf1.setFields(source.fields())
pf1.setAttributes(["test1", 1])
pf2 = QgsFeature()
pf2.setFields(source.fields())
pf2.setAttributes(["test2", 2])
assert pr.addFeatures([pf1, pf2])

setup = QgsEditorWidgetSetup('ValueMap', {"map": {"one": 1, "two": 2, "three": 3}})
source.setEditorWidgetSetup(1, setup)

exporter = QgsJSONExporter()
exporter.setVectorLayer(source)

expected = """{
"type":"Feature",
"id":0,
"geometry":null,
"properties":{
"fldtxt":"test1",
"fldint":"one"
}
}"""
self.assertEqual(exporter.exportFeature(pf1), expected)

def testExportFeatureCrs(self):
""" Test CRS transform when exporting features """

Expand Down Expand Up @@ -513,6 +560,27 @@ def testExportFeatureRelations(self):
}"""
self.assertEqual(exporter.exportFeature(pf2), expected)

# with field formatter
setup = QgsEditorWidgetSetup('ValueMap', {"map": {"apples": 123, "bananas": 124}})
child.setEditorWidgetSetup(1, setup)
expected = """{
"type":"Feature",
"id":0,
"geometry":null,
"properties":{
"fldtxt":"test1",
"fldint":67,
"foreignkey":123,
"relation one":[{"x":"foo",
"y":"apples",
"z":321},
{"x":"bar",
"y":"apples",
"z":654}]
}
}"""
self.assertEqual(exporter.exportFeature(pf1), expected)

# test excluding related attributes
exporter.setIncludeRelated(False)
self.assertEqual(exporter.includeRelated(), False)
Expand Down

0 comments on commit a5e3f19

Please sign in to comment.