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

Generated by: LCOV version 1.14