【问题标题】:Limiting the size of a json string while maintaining validity在保持有效性的同时限制 json 字符串的大小
【发布时间】:2021-08-11 22:45:50
【问题描述】:

我的特殊用例是记录 HTTP 请求参数、响应和标头。我希望能够只存储一部分数据,而不会丢失已存储的数据结构。

让我们举个简单的例子,HTTP 标头:

const JSON_OPTIONS = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;

function limitSizeOfHeaders(array $headers, int $maxSize) {
  $currentSize = 0;
  $limited = [];
  foreach($headers as $name => $values) {
    $headerSize = strlen(json_encode([$name => $values], JSON_OPTIONS)) - 1;
    if(($currentSize + $headerSize) > $maxSize) {
      break;
    }
    $limited[$name] = $values;
    $currentSize += $headerSize;
  }

  return $limited;
}

echo "\n".json_encode(limitSizeOfHeaders([
  'Authorization' => 'Bearer aX93nd11a9a0asaZzt432rSAu',
  'Content-Type' => 'form/urlencoded',
  'Accept' => 'application/json',
  'Cookie' => 'PHPSESSID=a9d78e29a2c150dee3'
], 120), JSON_OPTIONS|JSON_PRETTY_PRINT)."\n\n";

产生输出:

{
    "Authorization": "Bearer aX93nd11a9a0asaZzt432rSAu",
    "Content-Type": "form/urlencoded",
    "Accept": "application/json"
}

但标题具有非常可预测的结构。每个标头都是包含一个或多个字符串数组的对象的属性。一个 Json 响应体,可能没有那么简单。甚至表单数据也可以被解析为具有不同深度的多维数组。

所以我真正想要的是:

function limitArraySize(array $anything): array

我认为这是一个有趣的问题,其他人可能已经解决了。

【问题讨论】:

  • 我认为您必须自己跟踪解析堆栈,然后当您达到最终长度时,您就会知道有多少未终止的层。可能大多数时候,在达到 maxSize 之前,您还有一些回旋余地来添加终止符,或者如果没有,您可以删除一个元素。但是没有办法避免在您自己的逻辑中考虑输出语法。
  • 我不是在评判你,但我想不出我想记录标题的特定用例,我要么感兴趣在所有这些中,或者我对其中一些感兴趣,但会发现在不知道发生截断的情况下列出可能被截断的列表是可以接受的。
  • @ChrisHaas headers 只是一个例子。但我绝对想记录我发出的任何 API 请求的响应标头,最多几千字节,但我不希望它占用我所有的内存/硬盘空间,对于任何 json 响应都一样
  • 如果长度是一个问题,那么为什么要使用 PRETTY_PRINT?

标签: php arrays json storage


【解决方案1】:

这听起来像是数据损坏类型的坏主意……话虽如此,

您可以一直删除第一个最深的元素,直到编码大小合适为止。请注意,获取最深的元素很棘手,但可以通过递归函数来解决:

function limitArraySize(iterable $anything, int $max_length): iterable {
  $removeDeepest = function(iterable &$v)use(&$removeDeepest) {
    foreach($v as $key=>&$val) {
      if(!empty($val) && is_iterable($val)) {
        $removeDeepest($val);
        return;
      } else {
        unset($v[$key]);
        return;
      }
    }
  };
  for(;;) {
    $ret=json_encode(($anything));
    if(strlen($ret) < $max_length) {
      return $anything;
    }
    $removeDeepest($anything);
  }
}

在这里,每当$removeDeepest 检测到它没有处理最深的元素时,它就会一直调用自己更深的 1 个元素,直到它位于最深的元素并将其删除。

echo json_encode(limitArraySize([
  'Authorization' => 'Bearer aX93nd11a9a0asaZzt432rSAu',
  'Content-Type' => 'form/urlencoded',
  'Accept' => 'application/json',
  'Cookie' => 'PHPSESSID=a9d78e29a2c150dee3'
], 120), JSON_PRETTY_PRINT)."\n\n";

打印:

{
    "Content-Type": "form\/urlencoded",
    "Accept": "application\/json",
    "Cookie": "PHPSESSID=a9d78e29a2c150dee3"
}

edit:在任何地方扫描绝对最深的元素是很困难的。这个功能应该比上面那个更好,但绝对不是完美的(扫描绝对最深的元素是困难,这不是正确的做法,但它应该比上面的更接近):

function limitArraySize(iterable $anything, int $max_length): iterable{
    $getDepthShit=function(iterable $v)use(&$getDepthShit):int{
        $ret=0;
        foreach($v as $val){
            ++$ret;
            if(is_iterable($val)){
                $ret+=$getDepthShit($val);
            }
        }
        return $ret;
    };
    $removeDeepest = function(iterable &$v)use(&$removeDeepest,&$getDepthShit){
        $deeperKey = null;
        $deeperDepth = 0;
        foreach($v as $key=>&$val){
            if(!empty($val) && is_iterable($val)){
                $depth=$getDepthShit($val);
                if($depth > $deepestDepth){
                    $deeperKey=$key;
                    $deeperDepth=$depth;
                }
            }
        }
        if($deeperKey!==null){
            $removeDeepest($v[$deeperKey]);
        }else{
            unset($v[$key]);
        }
    };
    for(;;){
        $ret=json_encode(($anything));
        if(strlen($ret) < $max_length){
            return $anything;
        }
        $removeDeepest($anything);
    }
}

【讨论】:

  • 乍一看,这似乎删除了从第一个元素下降的最深元素,而不是整体上最深的元素。考虑:['a'=&gt;['b'=&gt;['c'=&gt;['d'=&gt;'deep'],'e'=&gt;'notsodeep']],'f'=&gt;['g'=&gt;['h'=&gt;['i'=&gt;['j'=&gt;['k'=&gt;['l'=&gt;['m'=&gt;'deepest']]]]]]]] 'deep' 和 'notsodeep' 将在 'deepest' 之前被删除。实际上,由于引用传递的一些怪癖,它进入了无限循环。但即使我哄它工作,它也是一个非常低效的解决方案,因为每次限制失败都会解析整个输入。所以虽然我感谢你的回答,但我不会使用这个。
  • 我也不明白你所说的数据损坏评论是什么意思。显然我们会丢失信息,但替代方案是 substr(json_encode($anything), 0, $max_length),这肯定会破坏我的数据。而且由于我每天要记录数十万个请求,因此存储不是无限的,而且我并不隐含地相信响应的服务器保存整个响应是不可行的
  • @kaan_a 哦,对不起,我忘记了&amp; 将其放入无限循环,将foreach($v as $key=&gt;$val) 替换为foreach($v as $key=&gt;&amp;$val) 并重试,它是否仍会进入无限循环?
  • 是的,解决了它
  • @kaan_a At first glance this would appear to remove the deepest element descended from the first element rather than the deepest element overall - 这是正确的。删除从第一个元素下降的最深元素比实际检测整个最深元素并删除它要容易得多。可以扫描绝对最深的元素,但这要困难得多 =/
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-07-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-24
相关资源
最近更新 更多