Skip to content
Permalink
Browse files
Use field formatter when exporting feature attributes to JSON
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 a5e3f1931b9e31ef6b766a0be116d0e687c11db7
Showing with 114 additions and 9 deletions.
  1. +8 −3 python/core/qgsjsonutils.sip
  2. +31 −4 src/core/qgsjsonutils.cpp
  3. +6 −1 src/core/qgsjsonutils.h
  4. +69 −1 tests/src/python/test_qgsjsonutils.py
@@ -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)
@@ -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.
@@ -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

@@ -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 )
@@ -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 )
{
@@ -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;
@@ -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++;
}
}
@@ -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;
@@ -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( '}' );
@@ -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
@@ -28,7 +28,8 @@
QgsLineString,
NULL,
QgsVectorLayer,
QgsRelation
QgsRelation,
QgsEditorWidgetSetup
)
from qgis.PyQt.QtCore import QVariant, QTextCodec

@@ -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()
@@ -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 """

@@ -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)

0 comments on commit a5e3f19

Please sign in to comment.