1
\$\begingroup\$

I wrote code sample below that is finding decimal values in jObject without '.' char, and convert them to long type. The issue is that it works for max jObject that contains two child jObjects.

public JObject ModifyDoubleIntegers(JObject objectToModify)
{
    JObject resultObjectModified = new JObject();
    foreach (var item in objectToModify)
    {
        if (item.Value.Type == JTokenType.Object)
        {
            var itemObject = (JObject)item.Value;
            var itemValues = new JObject();
            foreach (var childItem in itemObject)
            {
                if (childItem.Value.Type == JTokenType.Object)
                {
                    var grandChild = (JObject)childItem.Value;
                    var grandChildValues = new JObject();
                    foreach (var grandChildItem in grandChild)
                    {
                        if (grandChildItem.Value.Type == JTokenType.Float && !grandChildItem.Value.ToString().Contains('.'))
                        {
                            grandChildValues.Add(new JProperty(grandChildItem.Key, grandChildItem.Value.ToObject<long>()));
                        }
                        else
                        {
                            grandChildValues.Add(new JProperty(grandChildItem.Key, grandChildItem.Value));
                        }
                    }
                    itemValues.Add(childItem.Key, grandChildValues);
                }
                else
                {
                    if (childItem.Value.Type == JTokenType.Float &&
                        !childItem.Value.ToString().Contains('.'))
                    {
                        itemValues.Add(new JProperty(childItem.Key, childItem.Value.ToObject<long>()));
                    }
                    else
                    {
                        itemValues.Add(new JProperty(childItem.Key, childItem.Value));
                    }
                }
            }
            resultObjectModified.Add(item.Key, itemValues);
        }
        else
        {
            if (item.Value.Type == JTokenType.Float && !item.Value.ToString().Contains('.'))
            {
                resultObjectModified.Add(new JProperty(item.Key, item.Value.ToObject<long>()));
            }
            else
            {
                resultObjectModified.Add(new JProperty(item.Key, item.Value));
            }
        }
    }
    return resultObjectModified;
}

json response from API:

{
    "id": "F32C93C0AD7B489DA904C10DAE724ED0",
    "name": "Pixar 99",
    "amount1": null,
    "currency": "ZAR",
    "referenceDate": "2019-04-05T00:00:00+00:00",
    "dealTeamLeadUser": null,
    "dealTeamSecondaryUser": null,
    "projectName": null,
    "region": {
      "id": "7680EEE333C648E49135AF52205F7DDC",
      "name": "Shared"
    },
    "priority": {
      "code": 3,
      "description": "Medium"
    },
    "sourceType": {
      "code": "FUNDINV",
      "description": "Fund (invested)"
    },
    "sourceContact": {
      "id": "22F2185C9B0E4D6A9945D00DAA1CA220",
      "firstName": "Ingeborg",
      "lastName": "Actili"
    },
    "sourceCompany": {
      "id": "95495010D5C1470F98184819263CD20C",
      "name": "Aquila LP"
    },
    "dealType": {
      "code": "FOF",
      "description": "Fund of funds"
    },
    "number": 65,
    "workflow": {
      "code": "22684BFAD6EC416A861E94ACD07421CF",
      "description": "2 - Fund of Private Equity"
    },
    "assignedToUser": {
      "id": "D260BCC5D72C4CBDA8A0003AAEA26120",
      "displayName": "AddTypeEmpty"
    },
    "assignedToGroup": null,
    "supervisorUser": {
      "id": "72BFD2F2725E488E8BB852673042735B",
      "displayName": "QA TEST eFront"
    },
    "supervisorGroup": null,
    "status": {
      "code": "FPE_4",
      "description": "4 - Detailed evaluation in progress (PE Fund)"
    },
    "nextStepDate": "2020-04-15T00:00:00+00:00",
    "companyInfo": null,
    "closingDate": "2021-04-16T00:00:00+00:00",
    "expectedFinalCloseDate": "2022-04-06T00:00:00+00:00",
    "ourCommitment": 12000000,
    "targetIrr": 0.14,
    "targetTvpi": 3,
    "fundInfo": {
      "id": "346A51E0399341ABACFEE92F5B40A9BA",
      "region": {
        "id": "7680EEE333C648E49135AF52205F7DDC",
        "name": "Shared"
      },
      "name": "Antlia Opportunities Fund IV LLP",
      "shortName": "Antlia IV",
      "status": {
        "code": 301,
        "description": "Prospect Invested Fund"
      },
      "legalForm": {
        "code": "ZAF-LLP",
        "description": "ZAF - LLP - Limited Liability Partnership"
      },
      "nature": {
        "code": "50000000",
        "description": "50 - Telecommunication Services"
      },
      "category": null,
      "stage": {
        "code": "PE_MEZZ",
        "description": "PE - Mezzanine Capital"
      },
      "geography": {
        "code": "AFRICA_SOUTHERN",
        "description": "Southern Africa"
      },
      "vintageYear": 2011,
      "startDate": "2011-03-16T00:00:00+00:00",
      "closeDate": "2011-04-30T00:00:00+00:00",
      "endDate": null,
      "contractDate": null,
      "mangtFeesOutsideCommitment": false,
      "endInvestDate": "2015-04-30T00:00:00+00:00",
      "endPlannedDate": "2020-04-30T00:00:00+00:00",
      "endAddPlannedDate": null,
      "investmentPeriod": null,
      "fundDuration": null,
      "additionalYears": null,
      "clawback": false,
      "catchup": null,
      "managementFeesPercent": 0.01,
      "managementFeesPercent1": 0.01,
      "managementFeesMore": null,
      "managementFeesPolicy": null,
      "country": {
        "code": "ZA",
        "description": "South Africa"
      },
      "currency": "ZAR",
      "carriedInterest": 0.15,
      "carriedPerDeal": false,
      "hurdleRate": 0.08,
      "catchupRate": 0.02,
      "description": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
      "investmentPolicy": null,
      "distributionPolicy": null,
      "revenueDistribution": null,
      "secondHurdle": 0.03,
      "hurdleDescription": null,
      "secondCarried": 0.19,
      "carriedDescription": null,
      "managementFrequency": {
        "code": "QUATERLY"
      },
      "fundGroupBy": {
        "id": "FC4362360C8F4FF59D6161241E10110E",
        "name": "eFront Parallel Fund B"
      },
      "managementCompany": {
        "id": "FE5A9AAAFDE44E01BEA2AF85DF449855",
        "managementCompanyId": null,
        "name": "Antlia (Pty) Ltd"
      },
      "otherFund": {
        "id": "104CB83F393D409D8B3DED9FDCB8EEC9",
        "region": {
          "id": "7680EEE333C648E49135AF52205F7DDC",
          "name": "Shared"
        },
        "name": {
          "id": "346A51E0399341ABACFEE92F5B40A9BA",
          "name": "Antlia Opportunities Fund IV LLP"
        },
        "mngtFeesBeforeInvestment": null,
        "beforeMngtFeesBase": {
          "code": "COMMITMENT",
          "description": "Commitment"
        },
        "mngtFeesAfterInvestment": null,
        "afterMngtFeesBase": {
          "code": "NAV",
          "description": "NAV"
        },
        "feeOffset": null,
        "feeOffsetDescription": "1,00%",
        "hurdleBase": {
          "code": "1",
          "description": "Called capital"
        },
        "hurdleType": null,
        "size": 4500000000,
        "extensionMngtFeeBase": null,
        "minSize": null,
        "maxSize": null,
        "firstCloseSize": null
      },
      "hedgeFund": {
        "id": "2B1B5B97A441493BBD62D560C3E64CB7",
        "region": {
          "id": "7680EEE333C648E49135AF52205F7DDC",
          "name": "Shared"
        },
        "name": {
          "id": "346A51E0399341ABACFEE92F5B40A9BA",
          "name": "Antlia Opportunities Fund IV LLP"
        },
        "minimumInvestment": null,
        "managementFee": null,
        "redemptionFee": null,
        "incentiveFee": null,
        "otherFees": null,
        "managementFeeHelp": null,
        "redemptionFeeHelp": null,
        "incentiveFeeHelp": null,
        "otherFeesHelp": null,
        "admissionDates": null,
        "subscriptionFreq": null,
        "subscriptionFreqHelp": null,
        "redemptionFreq": null,
        "redemptionFreqHelp": null,
        "redemptionNoticeDays": null,
        "redemptionNotice": null,
        "redemptionPayout": null,
        "highWaterMarkText": null,
        "sidePocket": null,
        "sidePocketText": null,
        "newIssues": null,
        "newIssuesText": null,
        "reportingPeriod": null,
        "reportingStyle": null,
        "fundAssets": null,
        "domicile": null,
        "highWaterMark": null,
        "hurdleRate": null,
        "leverage": null,
        "crystallization": null,
        "hurdleRateHelp": null,
        "leverageHelp": null,
        "crystallizationHelp": null,
        "lockup": null,
        "lockupPeriod": null,
        "lockupPeriodHelp": null,
        "number1": null,
        "fiscalDay1": null,
        "fiscalMonth1": null,
        "initialVami": null,
        "benchmark1": null,
        "benchmark2": null
      },
      "firmContact": {
        "id": "CC9D6D4E77684E4D82314FA70705BF4E",
        "name": "Rawlinson, Anthony B."
      },
      "offerCoInvestment": null,
      "gpCommitment": 0.03,
      "mngFeesPercentExtension": null,
      "feeOffset": null,
      "carryEscrow": 0.23
    },
    "keyFigures": null,
    "investment": null,
    "comments": null,
    "description": null
  }
\$\endgroup\$

closed as unclear what you're asking by t3chb0t, Toby Speight, yuri, Peter Taylor, 200_success May 23 at 20:10

Please clarify your specific problem or add additional details to highlight exactly what you need. As it's currently written, it’s hard to tell exactly what you're asking. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.

  • \$\begingroup\$ Hi. What version of the Newtonsoft library you use? Also, does your code compile? \$\endgroup\$ – dfhwze May 22 at 16:05
  • \$\begingroup\$ and it works for max jObject that contains two child jObjects - why is this an issue? We'll also need some samples. Could you create a console app (or something) demonstrating how this code works? \$\endgroup\$ – t3chb0t May 22 at 16:19
  • \$\begingroup\$ also check this out for recursively finding leaf nodes in this monstrously written API (dotnetfiddle.net/5Iae0V) \$\endgroup\$ – dfhwze May 22 at 16:24
  • \$\begingroup\$ Welcome to Code Review! The current question title, which states your concerns about the code, is too general to be useful here. Please edit to the site standard, which is for the title to simply state the task accomplished by the code. Please see How to get the best value out of Code Review: Asking Questions for guidance on writing good question titles. \$\endgroup\$ – Toby Speight May 22 at 17:13
  • 1
    \$\begingroup\$ @t3chb0t I've added json response that I am going through, after parsing it with jObject.Parse. \$\endgroup\$ – miskegm May 23 at 11:52
2
\$\begingroup\$

Proposed Solution

public static JObject ModifyDoubleIntegers(JObject source) {
        if (source == null) return null;
        var target = new JObject(source);
        var properties = target.DescendantsAndSelf().Where(
            x => x.Type == JTokenType.Float).Select(x => x.Parent).OfType<JProperty>();
        foreach (var property in properties.Where(x
            => Regex.IsMatch(x.Value.ToObject<string>(), @"^\d*$")))
        {
            property.Value = property.Value.ToObject<long>();
        }
        return target;
    }

Remark

When parsing the sample json file you provided, all integers were parsed as JTokenType.Integer and all floats as JTokenType.Float, so I feel this entire problem is mute. Everything gets parsed correctly by calling JObject.Parse(json).

How did you end up having integer values in a JTokenType.Float?


Review

First of all, you don't need to loop n levels deep. There is a convenient method JContainer.DescendantsAndSelf that does the trick for you. I also changed the variable names to more common names.

public JObject ModifyDoubleIntegers(JObject objectToModify)
{
    JObject resultObjectModified = new JObject();
    foreach (var item in objectToModify)
    {
        ..
 public static JObject ModifyDoubleIntegers(JObject source) {
        if (source == null) return null;
        var target = new JObject(source);
        var properties = target.DescendantsAndSelf();
        ..

Now, to change the float values to long, you need to obtain all descendants of type JTokenType.Float and select their parent JProperty.

foreach (var grandChildItem in grandChild)
 {
     if (grandChildItem.Value.Type == JTokenType.Float))
     {
          ..
var properties = target.DescendantsAndSelf().Where(
     x => x.Type == JTokenType.Float).Select(x => x.Parent).OfType<JProperty>();

The last step is to change from float to long. I would not filter on Contains('.'), since this depends on your thread's CultureInfo. At my computer, I would have to check on Contains(','). [after remarks OP] We should use a context-free check on float. The best I can come up with is Regex.IsMatch(value, @"^\d*$").

 if (grandChildItem.Value.Type == JTokenType.Float 
           && !grandChildItem.Value.ToString().Contains('.'))      
  {
      grandChildValues.Add(new JProperty(
           grandChildItem.Key, grandChildItem.Value.ToObject<long>()));      
  }
foreach (var property in properties.Where(x
     => Regex.IsMatch(x.Value.ToObject<string>(), @"^\d*$")))
{ 
     property.Value = property.Value.ToObject<long>();
}

\$\endgroup\$
  • \$\begingroup\$ It is indeed a good solution, but still not working well. There are differences for all the float type JTokens with values other than integer. They are cutoff after the '.' ex: "hurdleRate": 0.08 becomes "hurdleRate": 0 . That was the reason I was checking for the '.' when I find the jTokenType.Float \$\endgroup\$ – miskegm May 23 at 16:33
  • \$\begingroup\$ @miskegm I am a bit confused on what exactly you want. I have provided a solution that does exactly the same as yours. Could you please specify what you expect from the code, that would make things a bit easier to follow :) \$\endgroup\$ – dfhwze May 23 at 16:36
  • \$\begingroup\$ @miskegm I think I get it now, you want all Integer values that are stored in a float to be stored a long. I will adapt my answer. \$\endgroup\$ – dfhwze May 23 at 18:57
  • \$\begingroup\$ Yes, exactly that, int values stored in float to be stored a long. It works like a charm now. Thanks a lot! \$\endgroup\$ – miskegm May 24 at 7:59
  • \$\begingroup\$ When parsing json response from server, the jToken float values like amount = 13000, becomes amount = 13000.0, so that is the reason fot this modification I am doing. \$\endgroup\$ – miskegm May 24 at 8:13

Not the answer you're looking for? Browse other questions tagged or ask your own question.