Эх сурвалжийг харах

Fix Empty ListValue/Struct json encoding (#5532)

* Fix Empty ListValue/Struct json encoding

* Add test for empty ListValue
Paul Yang 6 жил өмнө
parent
commit
11c979b591

+ 78 - 1
php/ext/google/protobuf/encode_decode.c

@@ -1099,6 +1099,12 @@ static void putrawmsg(MessageHeader* msg, const Descriptor* desc,
                       bool open_msg TSRMLS_DC);
                       bool open_msg TSRMLS_DC);
 static void putjsonany(MessageHeader* msg, const Descriptor* desc,
 static void putjsonany(MessageHeader* msg, const Descriptor* desc,
                        upb_sink* sink, int depth TSRMLS_DC);
                        upb_sink* sink, int depth TSRMLS_DC);
+static void putjsonlistvalue(
+    MessageHeader* msg, const Descriptor* desc,
+    upb_sink* sink, int depth TSRMLS_DC);
+static void putjsonstruct(
+    MessageHeader* msg, const Descriptor* desc,
+    upb_sink* sink, int depth TSRMLS_DC);
 
 
 static void putstr(zval* str, const upb_fielddef* f, upb_sink* sink,
 static void putstr(zval* str, const upb_fielddef* f, upb_sink* sink,
                    bool force_default);
                    bool force_default);
@@ -1342,17 +1348,88 @@ static void putjsonany(MessageHeader* msg, const Descriptor* desc,
   upb_sink_endmsg(sink, &status);
   upb_sink_endmsg(sink, &status);
 }
 }
 
 
+static void putjsonlistvalue(
+    MessageHeader* msg, const Descriptor* desc,
+    upb_sink* sink, int depth TSRMLS_DC) {
+  upb_status status;
+  upb_sink subsink;
+  const upb_fielddef* f = upb_msgdef_itof(desc->msgdef, 1);
+  uint32_t offset = desc->layout->fields[upb_fielddef_index(f)].offset;
+  zval* array;
+  RepeatedField* intern;
+  HashTable *ht;
+  int size, i;
+
+  upb_sink_startmsg(sink);
+
+  array = CACHED_PTR_TO_ZVAL_PTR(
+      DEREF(message_data(msg), offset, CACHED_VALUE*));
+  intern = UNBOX(RepeatedField, array);
+  ht = PHP_PROTO_HASH_OF(intern->array);
+  size = zend_hash_num_elements(ht);
+
+  if (size == 0) {
+    upb_sink_startseq(sink, getsel(f, UPB_HANDLER_STARTSEQ), &subsink);
+    upb_sink_endseq(sink, getsel(f, UPB_HANDLER_ENDSEQ));
+  } else {
+    putarray(array, f, sink, depth, true TSRMLS_CC);
+  }
+
+  upb_sink_endmsg(sink, &status);
+}
+
+static void putjsonstruct(
+    MessageHeader* msg, const Descriptor* desc,
+    upb_sink* sink, int depth TSRMLS_DC) {
+  upb_status status;
+  upb_sink subsink;
+  const upb_fielddef* f = upb_msgdef_itof(desc->msgdef, 1);
+  uint32_t offset = desc->layout->fields[upb_fielddef_index(f)].offset;
+  zval* map;
+  Map* intern;
+  int size;
+
+  upb_sink_startmsg(sink);
+
+  map = CACHED_PTR_TO_ZVAL_PTR(
+      DEREF(message_data(msg), offset, CACHED_VALUE*));
+  intern = UNBOX(Map, map);
+  size = upb_strtable_count(&intern->table);
+
+  if (size == 0) {
+    upb_sink_startseq(sink, getsel(f, UPB_HANDLER_STARTSEQ), &subsink);
+    upb_sink_endseq(sink, getsel(f, UPB_HANDLER_ENDSEQ));
+  } else {
+    putmap(map, f, sink, depth, true TSRMLS_CC);
+  }
+
+  upb_sink_endmsg(sink, &status);
+}
+
 static void putrawmsg(MessageHeader* msg, const Descriptor* desc,
 static void putrawmsg(MessageHeader* msg, const Descriptor* desc,
                       upb_sink* sink, int depth, bool is_json,
                       upb_sink* sink, int depth, bool is_json,
                       bool open_msg TSRMLS_DC) {
                       bool open_msg TSRMLS_DC) {
   upb_msg_field_iter i;
   upb_msg_field_iter i;
   upb_status status;
   upb_status status;
 
 
-  if (is_json && upb_msgdef_wellknowntype(desc->msgdef) == UPB_WELLKNOWN_ANY) {
+  if (is_json &&
+      upb_msgdef_wellknowntype(desc->msgdef) == UPB_WELLKNOWN_ANY) {
     putjsonany(msg, desc, sink, depth TSRMLS_CC);
     putjsonany(msg, desc, sink, depth TSRMLS_CC);
     return;
     return;
   }
   }
 
 
+  if (is_json &&
+      upb_msgdef_wellknowntype(desc->msgdef) == UPB_WELLKNOWN_LISTVALUE) {
+    putjsonlistvalue(msg, desc, sink, depth TSRMLS_CC);
+    return;
+  }
+
+  if (is_json &&
+      upb_msgdef_wellknowntype(desc->msgdef) == UPB_WELLKNOWN_STRUCT) {
+    putjsonstruct(msg, desc, sink, depth TSRMLS_CC);
+    return;
+  }
+
   if (open_msg) {
   if (open_msg) {
     upb_sink_startmsg(sink);
     upb_sink_startmsg(sink);
   }
   }

+ 36 - 0
php/src/Google/Protobuf/Internal/Message.php

@@ -1383,6 +1383,24 @@ class Message
             $timestamp = GPBUtil::formatTimestamp($this);
             $timestamp = GPBUtil::formatTimestamp($this);
             $timestamp = json_encode($timestamp);
             $timestamp = json_encode($timestamp);
             $output->writeRaw($timestamp, strlen($timestamp));
             $output->writeRaw($timestamp, strlen($timestamp));
+        } elseif (get_class($this) === 'Google\Protobuf\ListValue') {
+            $field = $this->desc->getField()[1];
+            if (!$this->existField($field)) {
+                $output->writeRaw("[]", 2);
+            } else {
+                if (!$this->serializeFieldToJsonStream($output, $field)) {
+                    return false;
+                }
+            }
+        } elseif (get_class($this) === 'Google\Protobuf\Struct') {
+            $field = $this->desc->getField()[1];
+            if (!$this->existField($field)) {
+                $output->writeRaw("{}", 2);
+            } else {
+                if (!$this->serializeFieldToJsonStream($output, $field)) {
+                    return false;
+                }
+            }
         } else {
         } else {
             if (!GPBUtil::hasSpecialJsonMapping($this)) {
             if (!GPBUtil::hasSpecialJsonMapping($this)) {
                 $output->writeRaw("{", 1);
                 $output->writeRaw("{", 1);
@@ -1844,6 +1862,24 @@ class Message
             $timestamp = GPBUtil::formatTimestamp($this);
             $timestamp = GPBUtil::formatTimestamp($this);
             $timestamp = json_encode($timestamp);
             $timestamp = json_encode($timestamp);
             $size += strlen($timestamp);
             $size += strlen($timestamp);
+        } elseif (get_class($this) === 'Google\Protobuf\ListValue') {
+            $field = $this->desc->getField()[1];
+            if ($this->existField($field)) {
+                $field_size = $this->fieldJsonByteSize($field);
+                $size += $field_size;
+            } else {
+                // Size for "[]".
+                $size += 2;
+            }
+        } elseif (get_class($this) === 'Google\Protobuf\Struct') {
+            $field = $this->desc->getField()[1];
+            if ($this->existField($field)) {
+                $field_size = $this->fieldJsonByteSize($field);
+                $size += $field_size;
+            } else {
+                // Size for "{}".
+                $size += 2;
+            }
         } else {
         } else {
             if (!GPBUtil::hasSpecialJsonMapping($this)) {
             if (!GPBUtil::hasSpecialJsonMapping($this)) {
                 // Size for "{}".
                 // Size for "{}".

+ 14 - 0
php/tests/encode_decode_test.php

@@ -898,6 +898,13 @@ class EncodeDecodeTest extends TestBase
         $this->assertSame("[1.5]", $m->serializeToJsonString());
         $this->assertSame("[1.5]", $m->serializeToJsonString());
     }
     }
 
 
+    public function testEncodeEmptyListValue()
+    {
+        $m = new Struct();
+        $m->setFields(['test' => (new Value())->setListValue(new ListValue())]);
+        $this->assertSame('{"test":[]}', $m->serializeToJsonString());
+    }
+
     public function testDecodeTopLevelStruct()
     public function testDecodeTopLevelStruct()
     {
     {
         $m = new Struct();
         $m = new Struct();
@@ -917,6 +924,13 @@ class EncodeDecodeTest extends TestBase
         $this->assertSame("{\"a\":1.5}", $m->serializeToJsonString());
         $this->assertSame("{\"a\":1.5}", $m->serializeToJsonString());
     }
     }
 
 
+    public function testEncodeEmptyStruct()
+    {
+        $m = new Struct();
+        $m->setFields(['test' => (new Value())->setStructValue(new Struct())]);
+        $this->assertSame('{"test":{}}', $m->serializeToJsonString());
+    }
+
     public function testDecodeTopLevelAny()
     public function testDecodeTopLevelAny()
     {
     {
         // Make sure packed message has been created at least once.
         // Make sure packed message has been created at least once.