Home > front end >  How to disable scientific notation for values like 0.000005 in PHP (json_encode)?
How to disable scientific notation for values like 0.000005 in PHP (json_encode)?

Time:08-17

I'm trying to integrate with some Partner API.

They only accept json with float type for amount.

Example:

  • OK
    • {"amount":0.0000005}
  • Error
    • {"amount":"0.0000005"}
    • {"amount":5.0E-7}

If the value greater or equal 1, then it's OK scenario always. But in my case I have values > 0, and < 1.

Code Example:

$arr = ['amount' => 0.0000005];
$str = json_encode($arr);

echo $str;

Output:

{"amount":5.0e-7}

I want the output to look like this:

{"amount":0.0000005}

Is it possible in php? May be some hacks & tricks?

CodePudding user response:

The cleanest I can think of is to traverse through the data, recursively substituting small numbers with a placeholder; then, after JSON encoding, replace the placeholders in the final JSON string with the number formatted how you want it.

The surprisingly difficult part is formatting the float itself; I found this existing question about how to do that with some working but not very elegant implementations. For brevity, I've left that part as a TODO below.

class JsonMangler
{
    private const THRESHOLD = 0.0001;
    private const PLACEHOLDER = '__PLACEHOLDER__';

    private array $mangledData = [];
    private array $substitutions = [];
    private int $placeholderIncrement = 0;

    public function __construct(array $realData) {
        // Start the recursive function
        $this->mangledData = $this->mangle($realData);
    }

    private function mangle(array $realData): array {
        $mangledData = [];

        foreach ( $realData as $key => $realValue ) {
            if ( is_float($realValue) && $realValue < self::THRESHOLD) {
                // Substitute small floats with a placeholder
                $substituteValue = self::PLACEHOLDER . ($this->placeholderIncrement  );
                $mangledData[$key] = $substituteValue;
                // Placeholder will appear in quotes in the JSON, which we want to replace away
                $this->substitutions["\"$substituteValue\""] = $this->formatFloat($realValue);
            }
            elseif ( is_array($realValue) ) {
                // Recurse through the data
                $mangledData[$key] = $this->mangle($realValue);
            }
            else {
                // Retain everything else
                $mangledData[$key] = $realValue;
            }
        }

        return $mangledData;
    }

    /**
     * Format a float into a string without any exponential notation
     */
    private function formatFloat(float $value): string
    {
        // This is surprisingly hard to do; see https://stackoverflow.com/q/22274437/157957
        return 'TODO';
    }

    public function getJson(int $jsonEncodeFlags = 0): string
    {
        $mangledJson = json_encode($this->mangledData, $jsonEncodeFlags);
        return str_replace(array_keys($this->substitutions), array_values($this->substitutions), $mangledJson);
    }
}

Using this implementation for formatFloat, the following test:

$example = [
    'amount' => 1.5,
    'small_amount' => 0.0001,
    'tiny_amount' => 0.0000005,
    'subobject' => [
        'sub_value' => 42.5,
        'tiny_sub_value' => 0.0000425,
        'array' => [
            1.23,
            0.0000123
        ]
    ]
];
echo (new JsonMangler($example))->getJson(JSON_PRETTY_PRINT);

Results in the following output:

{
    "amount": 1.5,
    "small_amount": 0.0001,
    "tiny_amount": 0.0000005,
    "subobject": {
        "sub_value": 42.5,
        "tiny_sub_value": 0.0000425,
        "array": [
            1.23,
            0.0000123
        ]
    }
}

CodePudding user response:

The only way to keep it the long way is to convert it to a string instead. But then it is not a number anymore!

$arr = ['amount' => number_format(0.0000005, 7)];
$str = json_encode($arr);

giving

{"amount":"0.0000005"}

Javascript itself would use the scientific notation:

j = {"amount":"0.0000005"};
parseFloat(j.amount);
5e-7

A hack would be to remove the quotes.

$quoteless = preg_replace('/:"(\d .\d )"/', ':$1', $str);
echo $quoteless;

will give

{"amount":0.0000005}
  • Related