LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/geojson - ogresrijsonreader.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 231 255 90.6 %
Date: 2025-05-31 00:00:17 Functions: 10 10 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OpenGIS Simple Features Reference Implementation
       4             :  * Purpose:  Implementation of OGRESRIJSONReader class (OGR ESRIJSON Driver)
       5             :  *           to read ESRI Feature Service REST data
       6             :  * Author:   Even Rouault, even dot rouault at spatialys.com
       7             :  *
       8             :  ******************************************************************************
       9             :  * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com>
      10             :  * Copyright (c) 2007, Mateusz Loskot
      11             :  * Copyright (c) 2013, Kyle Shannon <kyle at pobox dot com>
      12             :  *
      13             :  * SPDX-License-Identifier: MIT
      14             :  ****************************************************************************/
      15             : 
      16             : #include "cpl_port.h"
      17             : #include "ogrlibjsonutils.h"
      18             : 
      19             : #include <limits.h>
      20             : #include <stddef.h>
      21             : 
      22             : #include "cpl_conv.h"
      23             : #include "cpl_error.h"
      24             : #include "cpl_time.h"
      25             : #include "json.h"
      26             : // #include "json_object.h"
      27             : // #include "json_tokener.h"
      28             : #include "ogr_api.h"
      29             : #include "ogr_core.h"
      30             : #include "ogr_feature.h"
      31             : #include "ogr_geometry.h"
      32             : #include "ogr_spatialref.h"
      33             : #include "ogr_geojson.h"
      34             : #include "ogrgeojsonreader.h"
      35             : #include "ogrgeojsonutils.h"
      36             : #include "ogresrijsongeometry.h"
      37             : 
      38             : // #include "symbol_renames.h"
      39             : 
      40             : /************************************************************************/
      41             : /*                          OGRESRIJSONReader()                         */
      42             : /************************************************************************/
      43             : 
      44          21 : OGRESRIJSONReader::OGRESRIJSONReader() : poGJObject_(nullptr), poLayer_(nullptr)
      45             : {
      46          21 : }
      47             : 
      48             : /************************************************************************/
      49             : /*                         ~OGRESRIJSONReader()                         */
      50             : /************************************************************************/
      51             : 
      52          42 : OGRESRIJSONReader::~OGRESRIJSONReader()
      53             : {
      54          21 :     if (nullptr != poGJObject_)
      55             :     {
      56          21 :         json_object_put(poGJObject_);
      57             :     }
      58             : 
      59          21 :     poGJObject_ = nullptr;
      60          21 :     poLayer_ = nullptr;
      61          21 : }
      62             : 
      63             : /************************************************************************/
      64             : /*                           Parse()                                    */
      65             : /************************************************************************/
      66             : 
      67          21 : OGRErr OGRESRIJSONReader::Parse(const char *pszText)
      68             : {
      69          21 :     json_object *jsobj = nullptr;
      70          21 :     if (nullptr != pszText && !OGRJSonParse(pszText, &jsobj, true))
      71             :     {
      72           0 :         return OGRERR_CORRUPT_DATA;
      73             :     }
      74             : 
      75             :     // JSON tree is shared for while lifetime of the reader object
      76             :     // and will be released in the destructor.
      77          21 :     poGJObject_ = jsobj;
      78          21 :     return OGRERR_NONE;
      79             : }
      80             : 
      81             : /************************************************************************/
      82             : /*                           ReadLayers()                               */
      83             : /************************************************************************/
      84             : 
      85          21 : void OGRESRIJSONReader::ReadLayers(OGRGeoJSONDataSource *poDS,
      86             :                                    GeoJSONSourceType eSourceType)
      87             : {
      88          21 :     CPLAssert(nullptr == poLayer_);
      89             : 
      90          21 :     poDS->SetSupportsMGeometries(true);
      91             : 
      92          21 :     if (nullptr == poGJObject_)
      93             :     {
      94           0 :         CPLDebug("ESRIJSON",
      95             :                  "Missing parsed ESRIJSON data. Forgot to call Parse()?");
      96           0 :         return;
      97             :     }
      98             : 
      99          21 :     OGRSpatialReference *poSRS = OGRESRIJSONReadSpatialReference(poGJObject_);
     100             : 
     101          21 :     std::string osName = "ESRIJSON";
     102          21 :     if (eSourceType == eGeoJSONSourceFile)
     103             :     {
     104          19 :         osName = poDS->GetDescription();
     105          19 :         if (STARTS_WITH_CI(osName.c_str(), "ESRIJSON:"))
     106           1 :             osName = osName.substr(strlen("ESRIJSON:"));
     107          19 :         osName = CPLGetBasenameSafe(osName.c_str());
     108             :     }
     109             : 
     110          21 :     auto eGeomType = OGRESRIJSONGetGeometryType(poGJObject_);
     111          21 :     if (eGeomType == wkbNone)
     112             :     {
     113           1 :         if (poSRS)
     114             :         {
     115           0 :             eGeomType = wkbUnknown;
     116             :         }
     117             :         else
     118             :         {
     119             :             json_object *poObjFeatures =
     120           1 :                 OGRGeoJSONFindMemberByName(poGJObject_, "features");
     121           2 :             if (poObjFeatures &&
     122           1 :                 json_type_array == json_object_get_type(poObjFeatures))
     123             :             {
     124           1 :                 const auto nFeatures = json_object_array_length(poObjFeatures);
     125           1 :                 for (auto i = decltype(nFeatures){0}; i < nFeatures; ++i)
     126             :                 {
     127             :                     json_object *poObjFeature =
     128           1 :                         json_object_array_get_idx(poObjFeatures, i);
     129           2 :                     if (poObjFeature != nullptr &&
     130           1 :                         json_object_get_type(poObjFeature) == json_type_object)
     131             :                     {
     132           1 :                         if (auto poObjGeometry = OGRGeoJSONFindMemberByName(
     133             :                                 poObjFeature, "geometry"))
     134             :                         {
     135           1 :                             eGeomType = wkbUnknown;
     136             :                             poSRS =
     137           1 :                                 OGRESRIJSONReadSpatialReference(poObjGeometry);
     138           1 :                             break;
     139             :                         }
     140             :                     }
     141             :                 }
     142             :             }
     143             :         }
     144             :     }
     145             : 
     146          21 :     poLayer_ =
     147          21 :         new OGRGeoJSONLayer(osName.c_str(), poSRS, eGeomType, poDS, nullptr);
     148          21 :     poLayer_->SetSupportsMGeometries(true);
     149          21 :     if (poSRS != nullptr)
     150          10 :         poSRS->Release();
     151             : 
     152          21 :     if (!GenerateLayerDefn())
     153             :     {
     154           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     155             :                  "Layer schema generation failed.");
     156             : 
     157           0 :         delete poLayer_;
     158           0 :         return;
     159             :     }
     160             : 
     161          21 :     OGRGeoJSONLayer *poThisLayer = ReadFeatureCollection(poGJObject_);
     162          21 :     if (poThisLayer == nullptr)
     163             :     {
     164           0 :         delete poLayer_;
     165           0 :         return;
     166             :     }
     167             : 
     168          21 :     CPLErrorReset();
     169             : 
     170          21 :     poLayer_->DetectGeometryType();
     171          21 :     poDS->AddLayer(poLayer_);
     172             : }
     173             : 
     174             : /************************************************************************/
     175             : /*                        GenerateFeatureDefn()                         */
     176             : /************************************************************************/
     177             : 
     178          21 : bool OGRESRIJSONReader::GenerateLayerDefn()
     179             : {
     180          21 :     CPLAssert(nullptr != poGJObject_);
     181             : 
     182          21 :     bool bSuccess = true;
     183             : 
     184          21 :     OGRFeatureDefn *poDefn = poLayer_->GetLayerDefn();
     185          21 :     CPLAssert(nullptr != poDefn);
     186          21 :     CPLAssert(0 == poDefn->GetFieldCount());
     187          21 :     auto oTemporaryUnsealer(poDefn->GetTemporaryUnsealer());
     188             : 
     189             :     /* -------------------------------------------------------------------- */
     190             :     /*      Scan all features and generate layer definition.                */
     191             :     /* -------------------------------------------------------------------- */
     192          21 :     json_object *poFields = OGRGeoJSONFindMemberByName(poGJObject_, "fields");
     193          41 :     if (nullptr != poFields &&
     194          20 :         json_type_array == json_object_get_type(poFields))
     195             :     {
     196          20 :         const auto nFeatures = json_object_array_length(poFields);
     197          62 :         for (auto i = decltype(nFeatures){0}; i < nFeatures; ++i)
     198             :         {
     199          42 :             json_object *poField = json_object_array_get_idx(poFields, i);
     200          42 :             if (!ParseField(poField))
     201             :             {
     202           0 :                 CPLDebug("GeoJSON", "Create feature schema failure.");
     203           0 :                 bSuccess = false;
     204             :             }
     205             :         }
     206             :     }
     207           1 :     else if ((poFields = OGRGeoJSONFindMemberByName(
     208           1 :                   poGJObject_, "fieldAliases")) != nullptr &&
     209           0 :              json_object_get_type(poFields) == json_type_object)
     210             :     {
     211             :         json_object_iter it;
     212           0 :         it.key = nullptr;
     213           0 :         it.val = nullptr;
     214           0 :         it.entry = nullptr;
     215           0 :         json_object_object_foreachC(poFields, it)
     216             :         {
     217           0 :             OGRFieldDefn fldDefn(it.key, OFTString);
     218           0 :             poDefn->AddFieldDefn(&fldDefn);
     219             :         }
     220             :     }
     221             :     else
     222             :     {
     223             :         // Guess the fields' schema from the content of the features' "attributes"
     224             :         // element
     225             :         json_object *poObjFeatures =
     226           1 :             OGRGeoJSONFindMemberByName(poGJObject_, "features");
     227           2 :         if (poObjFeatures &&
     228           1 :             json_type_array == json_object_get_type(poObjFeatures))
     229             :         {
     230           2 :             gdal::DirectedAcyclicGraph<int, std::string> dag;
     231           2 :             std::vector<std::unique_ptr<OGRFieldDefn>> apoFieldDefn{};
     232           2 :             std::map<std::string, int> oMapFieldNameToIdx{};
     233           2 :             std::vector<int> anCurFieldIndices;
     234           2 :             std::set<int> aoSetUndeterminedTypeFields;
     235             : 
     236           1 :             const auto nFeatures = json_object_array_length(poObjFeatures);
     237           2 :             for (auto i = decltype(nFeatures){0}; i < nFeatures; ++i)
     238             :             {
     239             :                 json_object *poObjFeature =
     240           1 :                     json_object_array_get_idx(poObjFeatures, i);
     241           2 :                 if (poObjFeature != nullptr &&
     242           1 :                     json_object_get_type(poObjFeature) == json_type_object)
     243             :                 {
     244           1 :                     int nPrevFieldIdx = -1;
     245             : 
     246             :                     json_object *poObjProps =
     247           1 :                         OGRGeoJSONFindMemberByName(poObjFeature, "attributes");
     248           2 :                     if (nullptr != poObjProps &&
     249           1 :                         json_object_get_type(poObjProps) == json_type_object)
     250             :                     {
     251             :                         json_object_iter it;
     252           1 :                         it.key = nullptr;
     253           1 :                         it.val = nullptr;
     254           1 :                         it.entry = nullptr;
     255           2 :                         json_object_object_foreachC(poObjProps, it)
     256             :                         {
     257           1 :                             anCurFieldIndices.clear();
     258           1 :                             OGRGeoJSONReaderAddOrUpdateField(
     259             :                                 anCurFieldIndices, oMapFieldNameToIdx,
     260           1 :                                 apoFieldDefn, it.key, it.val,
     261             :                                 /*bFlattenNestedAttributes = */ true,
     262             :                                 /* chNestedAttributeSeparator = */ '.',
     263             :                                 /* bArrayAsString =*/false,
     264             :                                 /* bDateAsString = */ false,
     265             :                                 aoSetUndeterminedTypeFields);
     266           2 :                             for (int idx : anCurFieldIndices)
     267             :                             {
     268           2 :                                 dag.addNode(idx,
     269           1 :                                             apoFieldDefn[idx]->GetNameRef());
     270           1 :                                 if (nPrevFieldIdx != -1)
     271             :                                 {
     272           0 :                                     dag.addEdge(nPrevFieldIdx, idx);
     273             :                                 }
     274           1 :                                 nPrevFieldIdx = idx;
     275             :                             }
     276             :                         }
     277             :                     }
     278             :                 }
     279             :             }
     280             : 
     281           2 :             const auto sortedFields = dag.getTopologicalOrdering();
     282           1 :             CPLAssert(sortedFields.size() == apoFieldDefn.size());
     283           2 :             for (int idx : sortedFields)
     284             :             {
     285             :                 // cppcheck-suppress containerOutOfBounds
     286           1 :                 poDefn->AddFieldDefn(apoFieldDefn[idx].get());
     287             :             }
     288             :         }
     289             :     }
     290             : 
     291          42 :     return bSuccess;
     292             : }
     293             : 
     294             : /************************************************************************/
     295             : /*                             ParseField()                             */
     296             : /************************************************************************/
     297             : 
     298          42 : bool OGRESRIJSONReader::ParseField(json_object *poObj)
     299             : {
     300          42 :     OGRFeatureDefn *poDefn = poLayer_->GetLayerDefn();
     301          42 :     CPLAssert(nullptr != poDefn);
     302             : 
     303          42 :     bool bSuccess = false;
     304             : 
     305             :     /* -------------------------------------------------------------------- */
     306             :     /*      Read collection of properties.                                  */
     307             :     /* -------------------------------------------------------------------- */
     308          42 :     json_object *poObjName = OGRGeoJSONFindMemberByName(poObj, "name");
     309          42 :     json_object *poObjType = OGRGeoJSONFindMemberByName(poObj, "type");
     310          42 :     if (nullptr != poObjName && nullptr != poObjType)
     311             :     {
     312          42 :         OGRFieldType eFieldType = OFTString;
     313          42 :         OGRFieldSubType eFieldSubType = OFSTNone;
     314          42 :         const char *pszObjName = json_object_get_string(poObjName);
     315          42 :         const char *pszObjType = json_object_get_string(poObjType);
     316          42 :         if (EQUAL(pszObjType, "esriFieldTypeString"))
     317             :         {
     318             :             // do nothing
     319             :         }
     320          34 :         else if (EQUAL(pszObjType, "esriFieldTypeOID"))
     321             :         {
     322          12 :             eFieldType = OFTInteger;
     323          12 :             poLayer_->SetFIDColumn(pszObjName);
     324             :         }
     325          22 :         else if (EQUAL(pszObjType, "esriFieldTypeSingle"))
     326             :         {
     327           2 :             eFieldType = OFTReal;
     328           2 :             eFieldSubType = OFSTFloat32;
     329             :         }
     330          20 :         else if (EQUAL(pszObjType, "esriFieldTypeDouble"))
     331             :         {
     332           8 :             eFieldType = OFTReal;
     333             :         }
     334          12 :         else if (EQUAL(pszObjType, "esriFieldTypeSmallInteger"))
     335             :         {
     336           2 :             eFieldType = OFTInteger;
     337           2 :             eFieldSubType = OFSTInt16;
     338             :         }
     339          10 :         else if (EQUAL(pszObjType, "esriFieldTypeInteger"))
     340             :         {
     341           8 :             eFieldType = OFTInteger;
     342             :         }
     343           2 :         else if (EQUAL(pszObjType, "esriFieldTypeDate"))
     344             :         {
     345           2 :             eFieldType = OFTDateTime;
     346             :         }
     347             :         else
     348             :         {
     349           0 :             CPLDebug("ESRIJSON",
     350             :                      "Unhandled fields[\"%s\"].type = %s. "
     351             :                      "Processing it as a String",
     352             :                      pszObjName, pszObjType);
     353             :         }
     354          42 :         OGRFieldDefn fldDefn(pszObjName, eFieldType);
     355          42 :         fldDefn.SetSubType(eFieldSubType);
     356             : 
     357          42 :         if (eFieldType != OFTDateTime)
     358             :         {
     359             :             json_object *const poObjLength =
     360          40 :                 OGRGeoJSONFindMemberByName(poObj, "length");
     361          58 :             if (poObjLength != nullptr &&
     362          18 :                 json_object_get_type(poObjLength) == json_type_int)
     363             :             {
     364          18 :                 const int nWidth = json_object_get_int(poObjLength);
     365             :                 // A dummy width of 2147483647 seems to indicate no known field with
     366             :                 // which in the OGR world is better modelled as 0 field width.
     367             :                 // (#6529)
     368          18 :                 if (nWidth != INT_MAX)
     369          18 :                     fldDefn.SetWidth(nWidth);
     370             :             }
     371             :         }
     372             : 
     373          42 :         json_object *poObjAlias = OGRGeoJSONFindMemberByName(poObj, "alias");
     374          42 :         if (poObjAlias && json_object_get_type(poObjAlias) == json_type_string)
     375             :         {
     376          42 :             const char *pszAlias = json_object_get_string(poObjAlias);
     377          42 :             if (strcmp(pszObjName, pszAlias) != 0)
     378          12 :                 fldDefn.SetAlternativeName(pszAlias);
     379             :         }
     380             : 
     381          42 :         poDefn->AddFieldDefn(&fldDefn);
     382             : 
     383          42 :         bSuccess = true;
     384             :     }
     385          42 :     return bSuccess;
     386             : }
     387             : 
     388             : /************************************************************************/
     389             : /*                           AddFeature                                 */
     390             : /************************************************************************/
     391             : 
     392          20 : bool OGRESRIJSONReader::AddFeature(OGRFeature *poFeature)
     393             : {
     394          20 :     if (nullptr == poFeature)
     395           0 :         return false;
     396             : 
     397          20 :     poLayer_->AddFeature(poFeature);
     398          20 :     delete poFeature;
     399             : 
     400          20 :     return true;
     401             : }
     402             : 
     403             : /************************************************************************/
     404             : /*                           EsriDateToOGRDate()                        */
     405             : /************************************************************************/
     406             : 
     407           2 : static void EsriDateToOGRDate(int64_t nVal, OGRField *psField)
     408             : {
     409           2 :     const auto nSeconds = nVal / 1000;
     410           2 :     const auto nMillisec = static_cast<int>(nVal % 1000);
     411             : 
     412             :     struct tm brokendowntime;
     413           2 :     CPLUnixTimeToYMDHMS(nSeconds, &brokendowntime);
     414             : 
     415           2 :     psField->Date.Year = static_cast<GInt16>(brokendowntime.tm_year + 1900);
     416           2 :     psField->Date.Month = static_cast<GByte>(brokendowntime.tm_mon + 1);
     417           2 :     psField->Date.Day = static_cast<GByte>(brokendowntime.tm_mday);
     418           2 :     psField->Date.Hour = static_cast<GByte>(brokendowntime.tm_hour);
     419           2 :     psField->Date.Minute = static_cast<GByte>(brokendowntime.tm_min);
     420           2 :     psField->Date.Second =
     421           2 :         static_cast<float>(brokendowntime.tm_sec + nMillisec / 1000.0);
     422           2 :     psField->Date.TZFlag = 100;
     423           2 :     psField->Date.Reserved = 0;
     424           2 : }
     425             : 
     426             : /************************************************************************/
     427             : /*                           ReadFeature()                              */
     428             : /************************************************************************/
     429             : 
     430          20 : OGRFeature *OGRESRIJSONReader::ReadFeature(json_object *poObj)
     431             : {
     432          20 :     CPLAssert(nullptr != poObj);
     433          20 :     CPLAssert(nullptr != poLayer_);
     434             : 
     435          20 :     OGRFeature *poFeature = new OGRFeature(poLayer_->GetLayerDefn());
     436             : 
     437             :     /* -------------------------------------------------------------------- */
     438             :     /*      Translate ESRIJSON "attributes" object to feature attributes.   */
     439             :     /* -------------------------------------------------------------------- */
     440          20 :     CPLAssert(nullptr != poFeature);
     441             : 
     442          20 :     json_object *poObjProps = OGRGeoJSONFindMemberByName(poObj, "attributes");
     443          39 :     if (nullptr != poObjProps &&
     444          19 :         json_object_get_type(poObjProps) == json_type_object)
     445             :     {
     446          19 :         OGRFieldDefn *poFieldDefn = nullptr;
     447             :         json_object_iter it;
     448          19 :         it.key = nullptr;
     449          19 :         it.val = nullptr;
     450          19 :         it.entry = nullptr;
     451          62 :         json_object_object_foreachC(poObjProps, it)
     452             :         {
     453          43 :             const int nField = poFeature->GetFieldIndex(it.key);
     454          43 :             if (nField >= 0)
     455             :             {
     456          43 :                 poFieldDefn = poFeature->GetFieldDefnRef(nField);
     457          43 :                 if (poFieldDefn && it.val != nullptr)
     458             :                 {
     459          43 :                     if (EQUAL(it.key, poLayer_->GetFIDColumn()))
     460          12 :                         poFeature->SetFID(json_object_get_int(it.val));
     461          43 :                     switch (poLayer_->GetLayerDefn()
     462          43 :                                 ->GetFieldDefn(nField)
     463          43 :                                 ->GetType())
     464             :                     {
     465          22 :                         case OFTInteger:
     466             :                         {
     467          22 :                             poFeature->SetField(nField,
     468          22 :                                                 json_object_get_int(it.val));
     469          22 :                             break;
     470             :                         }
     471          10 :                         case OFTReal:
     472             :                         {
     473          10 :                             poFeature->SetField(nField,
     474          10 :                                                 json_object_get_double(it.val));
     475          10 :                             break;
     476             :                         }
     477           2 :                         case OFTDateTime:
     478             :                         {
     479           2 :                             const auto nVal = json_object_get_int64(it.val);
     480           2 :                             EsriDateToOGRDate(
     481             :                                 nVal, poFeature->GetRawFieldRef(nField));
     482           2 :                             break;
     483             :                         }
     484           9 :                         default:
     485             :                         {
     486           9 :                             poFeature->SetField(nField,
     487             :                                                 json_object_get_string(it.val));
     488           9 :                             break;
     489             :                         }
     490             :                     }
     491             :                 }
     492             :             }
     493             :         }
     494             :     }
     495             : 
     496          20 :     const OGRwkbGeometryType eType = poLayer_->GetGeomType();
     497          20 :     if (eType == wkbNone)
     498           0 :         return poFeature;
     499             : 
     500             :     /* -------------------------------------------------------------------- */
     501             :     /*      Translate geometry sub-object of ESRIJSON Feature.               */
     502             :     /* -------------------------------------------------------------------- */
     503          20 :     json_object *poObjGeom = nullptr;
     504          20 :     json_object *poTmp = poObj;
     505             :     json_object_iter it;
     506          20 :     it.key = nullptr;
     507          20 :     it.val = nullptr;
     508          20 :     it.entry = nullptr;
     509          55 :     json_object_object_foreachC(poTmp, it)
     510             :     {
     511          37 :         if (EQUAL(it.key, "geometry"))
     512             :         {
     513          20 :             if (it.val != nullptr)
     514          18 :                 poObjGeom = it.val;
     515             :             // We're done.  They had 'geometry':null.
     516             :             else
     517           2 :                 return poFeature;
     518             :         }
     519             :     }
     520             : 
     521          18 :     if (nullptr != poObjGeom)
     522             :     {
     523          18 :         OGRGeometry *poGeometry = OGRESRIJSONReadGeometry(poObjGeom);
     524          18 :         if (nullptr != poGeometry)
     525             :         {
     526          18 :             poFeature->SetGeometryDirectly(poGeometry);
     527             :         }
     528             :     }
     529             : 
     530          18 :     return poFeature;
     531             : }
     532             : 
     533             : /************************************************************************/
     534             : /*                           ReadFeatureCollection()                    */
     535             : /************************************************************************/
     536             : 
     537          21 : OGRGeoJSONLayer *OGRESRIJSONReader::ReadFeatureCollection(json_object *poObj)
     538             : {
     539          21 :     CPLAssert(nullptr != poLayer_);
     540             : 
     541          21 :     json_object *poObjFeatures = OGRGeoJSONFindMemberByName(poObj, "features");
     542          21 :     if (nullptr == poObjFeatures)
     543             :     {
     544           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     545             :                  "Invalid FeatureCollection object. "
     546             :                  "Missing \'features\' member.");
     547           0 :         return nullptr;
     548             :     }
     549             : 
     550          21 :     if (json_type_array == json_object_get_type(poObjFeatures))
     551             :     {
     552          21 :         const auto nFeatures = json_object_array_length(poObjFeatures);
     553          41 :         for (auto i = decltype(nFeatures){0}; i < nFeatures; ++i)
     554             :         {
     555             :             json_object *poObjFeature =
     556          20 :                 json_object_array_get_idx(poObjFeatures, i);
     557          40 :             if (poObjFeature != nullptr &&
     558          20 :                 json_object_get_type(poObjFeature) == json_type_object)
     559             :             {
     560             :                 OGRFeature *poFeature =
     561          20 :                     OGRESRIJSONReader::ReadFeature(poObjFeature);
     562          20 :                 AddFeature(poFeature);
     563             :             }
     564             :         }
     565             :     }
     566             : 
     567             :     // We're returning class member to follow the same pattern of
     568             :     // Read* functions call convention.
     569          21 :     CPLAssert(nullptr != poLayer_);
     570          21 :     return poLayer_;
     571             : }

Generated by: LCOV version 1.14