# Read an object and its properties

Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Guides](https://developer.m-files.com/APIs/gRPC-API/index.md#guides)

HTML version: https://developer.m-files.com/APIs/gRPC-API/guides/read-object/

Given an object's type and ID (for example from a search), get its latest version, its files and
all its property values, and turn the typed values into text.

## Calls

[GetObjectInfoAndProperties](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetObjectInfoAndProperties/index.md) with:

| Field | Value |
|---|---|
| `obj_id` | The object: `type` is the object type ID, `item_id.internal_id` the object ID |
| `latest_version` | true for the latest version; false to read the version in `doc_version` |
| `allow_checked_out` | Optional: true to get your own checked-out version when the object is checked out to you (the recipe leaves it false) |

The response has `object_info` ([ObjectVersionEx](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ObjectVersionEx/index.md): title, version,
files, check-out state) and `property_array`, a list of
[PropertyValue](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.PropertyValue/index.md). A property value carries only the property
definition ID; get the names from
[GetPropertyDefs](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCPropertyDefs/GetPropertyDefs/index.md) (and cache them).

Each value is a [TypedValue](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.TypedValue/index.md): `type` is the data type, and one field of
the `data` oneof holds the value:

| Data type | Field of `data` | Notes |
|---|---|---|
| `DATATYPE_TEXT`, `DATATYPE_MULTI_LINE_TEXT` | `text`, `multi_line_text` | |
| `DATATYPE_INTEGER`, `DATATYPE_INTEGER64`, `DATATYPE_REAL_NUMBER` | `integer`, `integer64`, `real_number` | |
| `DATATYPE_BOOLEAN` | `boolean` | |
| `DATATYPE_DATE` | `date` | A `Timestamp` at midnight UTC of the date |
| `DATATYPE_TIMESTAMP` | `timestamp` | UTC |
| `DATATYPE_LOOKUP` | `lookup` | `value_list_item_info.obj_id` (value list and item ID) and `.name` |
| `DATATYPE_MULTI_SELECT_LOOKUP` | `multi_select_lookup.values` | A list of lookups |

An empty value has `is_null_value` true and no `data` field set.

## Code

The recipe reads object 437 of object type 0 by default; pass another object ID and object type
as arguments. `format_value` (`FormatValue` in C# and Go) turns a typed value into text: it
checks `is_null_value`, then which member of the `data` oneof is set.

### Python

```python
def format_value(value: structs.TypedValue) -> str:
    """A typed value as text: lookups show their names and IDs."""
    if value.is_null_value:
        return "(empty)"
    data = value.data
    kind = data.WhichOneof("data")  # The member of the oneof that is set.
    if kind in ("text", "multi_line_text", "integer", "integer64", "real_number",
                "decimal_number"):
        return str(getattr(data, kind))
    if kind == "boolean":
        return "yes" if data.boolean else "no"
    if kind == "date":  # Midnight UTC of the day.
        return f"{data.date.ToDatetime():%Y-%m-%d}"
    if kind == "timestamp":
        return f"{data.timestamp.ToDatetime():%Y-%m-%d %H:%M:%S} UTC"
    if kind == "lookup":
        return _format_lookup(data.lookup)
    if kind == "multi_select_lookup":
        return "; ".join(_format_lookup(item) for item in data.multi_select_lookup.values)
    return f"({value.type})"


def _format_lookup(item: structs.Lookup) -> str:
    info = item.value_list_item_info
    return f"{info.name} (ID {info.obj_id.item_id.internal_id})"
```

*Source: [python/recipes/recipe_helpers.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/recipe_helpers.py)*

```python
# Property names, by ID. A real application caches these (they change rarely).
property_defs = client.stub(IRPCPropertyDefs_pb2_grpc.IRPCPropertyDefsStub)
names = {p.id: p.name for p in property_defs.GetPropertyDefs(
    IRPCPropertyDefs_pb2.GetPropertyDefsRequest()).property_defs}

# The latest version and its properties in one call.
objects = client.stub(IRPCObjectOperations_pb2_grpc.IRPCObjectOperationsStub)
response = objects.GetObjectInfoAndProperties(
    IRPCObjectOperations_pb2.GetObjectInfoAndPropertiesRequest(
        obj_id=obj_id(object_type, item_id),
        latest_version=True))

print(describe(response.object_info))
for prop in response.property_array:
    name = names.get(prop.property_def, "?")
    print(f"  {name} ({prop.property_def}): {format_value(prop.value)}")
```

*Source: [python/recipes/read_object.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/read_object.py)*

### C#

```csharp
/// <summary>A typed value as text: lookups show their names and IDs.</summary>
public static string FormatValue(TypedValue value)
{
    if (value.IsNullValue)
        return "(empty)";
    var data = value.Data;
    return data.DataCase switch
    {
        TypedValueUnion.DataOneofCase.Text => data.Text,
        TypedValueUnion.DataOneofCase.MultiLineText => data.MultiLineText,
        TypedValueUnion.DataOneofCase.Integer => data.Integer.ToString(CultureInfo.InvariantCulture),
        TypedValueUnion.DataOneofCase.Integer64 => data.Integer64.ToString(CultureInfo.InvariantCulture),
        TypedValueUnion.DataOneofCase.RealNumber => data.RealNumber.ToString(CultureInfo.InvariantCulture),
        TypedValueUnion.DataOneofCase.DecimalNumber => data.DecimalNumber,
        TypedValueUnion.DataOneofCase.Boolean => data.Boolean ? "yes" : "no",
        // A date is midnight UTC of the day.
        TypedValueUnion.DataOneofCase.Date => data.Date.ToDateTime().ToString("yyyy-MM-dd"),
        TypedValueUnion.DataOneofCase.Timestamp =>
            data.Timestamp.ToDateTime().ToString("yyyy-MM-dd HH:mm:ss") + " UTC",
        TypedValueUnion.DataOneofCase.Lookup => FormatLookup(data.Lookup),
        TypedValueUnion.DataOneofCase.MultiSelectLookup =>
            string.Join("; ", data.MultiSelectLookup.Values.Select(FormatLookup)),
        _ => $"({value.Type})",
    };
}

private static string FormatLookup(Lookup lookup) =>
    $"{lookup.ValueListItemInfo.Name} (ID {lookup.ValueListItemInfo.ObjId.ItemId.InternalId})";
```

*Source: [csharp/Recipes/RecipeHelpers.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Recipes/RecipeHelpers.cs)*

```csharp
// Property names, by ID. A real application caches these (they change rarely).
var propertyDefs = new IRPCPropertyDefs.IRPCPropertyDefsClient(connection.CallInvoker);
var names = (await propertyDefs.GetPropertyDefsAsync(new GetPropertyDefsRequest()))
    .PropertyDefs.ToDictionary(p => p.Id, p => p.Name);

// The latest version and its properties in one call.
var objects = new IRPCObjectOperations.IRPCObjectOperationsClient(connection.CallInvoker);
var response = await objects.GetObjectInfoAndPropertiesAsync(new GetObjectInfoAndPropertiesRequest
{
    ObjId = ObjId(objectType, id),
    LatestVersion = true,
});

Console.WriteLine(RecipeHelpers.Describe(response.ObjectInfo));
foreach (var property in response.PropertyArray)
{
    string name = names.GetValueOrDefault(property.PropertyDef, "?");
    Console.WriteLine($"  {name} ({property.PropertyDef}): {RecipeHelpers.FormatValue(property.Value)}");
}
```

*Source: [csharp/Recipes/ReadObject.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Recipes/ReadObject.cs)*

### Go

```go

// FormatValue prints a typed value; lookups show their names and IDs. The value's Data holds
// one member of the TypedValueUnion oneof, as one of the TypedValueUnion_* wrapper types.
func FormatValue(value *mfiles.TypedValue) string {
	if value.GetIsNullValue() {
		return "(empty)"
	}
	switch data := value.GetData().GetData().(type) {
	case *mfiles.TypedValueUnion_Text:
		return data.Text
	case *mfiles.TypedValueUnion_MultiLineText:
		return data.MultiLineText
	case *mfiles.TypedValueUnion_Integer:
		return strconv.Itoa(int(data.Integer))
	case *mfiles.TypedValueUnion_Integer64:
		return strconv.FormatInt(data.Integer64, 10)
	case *mfiles.TypedValueUnion_RealNumber:
		return strconv.FormatFloat(data.RealNumber, 'g', -1, 64)
	case *mfiles.TypedValueUnion_DecimalNumber:
		return data.DecimalNumber
	case *mfiles.TypedValueUnion_Boolean:
		if data.Boolean {
			return "yes"
		}
		return "no"
	case *mfiles.TypedValueUnion_Date: // midnight UTC of the day
		return data.Date.AsTime().Format(time.DateOnly)
	case *mfiles.TypedValueUnion_Timestamp:
		return data.Timestamp.AsTime().Format(time.DateTime) + " UTC"
	case *mfiles.TypedValueUnion_Lookup:
		return formatLookup(data.Lookup)
	case *mfiles.TypedValueUnion_MultiSelectLookup:
		items := make([]string, 0, len(data.MultiSelectLookup.GetValues()))
		for _, lookup := range data.MultiSelectLookup.GetValues() {
			items = append(items, formatLookup(lookup))
		}
		return strings.Join(items, "; ")
	default:
		return fmt.Sprintf("(%s)", value.GetType())
	}
}

func formatLookup(lookup *mfiles.Lookup) string {
	item := lookup.GetValueListItemInfo()
	return fmt.Sprintf("%s (ID %d)", item.GetName(), item.GetObjId().GetItemId().GetInternalId())
}

```

*Source: [go/recipes/internal/recipe/recipe.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/recipes/internal/recipe/recipe.go)*

```go
// Property names, by ID. A real application caches these (they change rarely).
propertyDefs := mfiles.NewIRPCPropertyDefsClient(client.Conn)
defs, err := propertyDefs.GetPropertyDefs(ctx, &mfiles.GetPropertyDefsRequest{})
if err != nil {
	return err
}
names := map[int32]string{}
for _, def := range defs.GetPropertyDefs() {
	names[def.GetId()] = def.GetName()
}

// The latest version and its properties in one call.
objects := mfiles.NewIRPCObjectOperationsClient(client.Conn)
response, err := objects.GetObjectInfoAndProperties(ctx,
	&mfiles.GetObjectInfoAndPropertiesRequest{
		ObjId:         mfilesclient.ObjID(objectType, id),
		LatestVersion: true,
	})
if err != nil {
	return err
}

fmt.Println(recipe.Describe(response.GetObjectInfo()))
for _, property := range response.GetPropertyArray() {
	name, ok := names[property.GetPropertyDef()]
	if !ok {
		name = "?"
	}
	fmt.Printf("  %s (%d): %s\n", name, property.GetPropertyDef(),
		recipe.FormatValue(property.GetValue()))
}
```

*Source: [go/recipes/read_object/main.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/recipes/read_object/main.go)*

Output for a document of the Sample Vault:

```text
Proposal 7732 - ESTT Corporation (IT) (ID 437, version 1, modified 2012-06-07 10:39:43 UTC)
  Name or title (0): Proposal 7732 - ESTT Corporation (IT)
  Last modified (21): 2012-06-07 10:39:43 UTC
  Single file (22): yes
  Last modified by (23): Andy Nash (ID 21)
  Status changed (24): 2012-06-07 10:39:43 UTC
  Size on server (this version) (30): 57344
  Class (100): Proposal (ID 5)
  Class groups (101): 1. PURCHASING AND SALES (ID 3)
  Document date (1002): 2005-03-02
  Keywords (1004): (empty)
  Description (1034): (empty)
  Document (1076): Order - Graphical Design (ID 293)
  Project (1078): Redesign of ESTT Marketing Material (ID 34)
  Customer (1079): ESTT Corporation (IT) (ID 141)
  Effective through (1096): 2007-08-01
  Accepted (1098): yes
  Proposal title (1102): Proposal 7732 - ESTT Corporation (IT)
  Proposal number (1103): 7732
  Created (20): 2012-06-07 10:39:31 UTC
  Created by (25): Andy Nash (ID 21)
  Size on server (all versions) (31): 57344
  Marked for archiving (32): no
  Object changed (89): 2012-06-07 10:39:43 UTC
```

## Pitfalls

* **Check the oneof, not the type.** Read the value from the field that `data` actually has set
  (`WhichOneof` in Python), and check `is_null_value` first.
* **Lookup items can be in any value list.** The `obj_id.type` of a lookup is the value list or
  object type; in proto3 a 0 (the Document type) is not sent, so an unset `type` means 0.
* **Dates have no time zone.** A `DATATYPE_DATE` value is a date; print it in UTC, or it can
  shift by a day.
* **Not found.** An object that does not exist, or that the user cannot see, fails with
  `0x8004000B` "Not found." See [Handle errors](https://developer.m-files.com/APIs/gRPC-API/guides/errors/index.md).
* The property list can contain properties that the object's class does not list (1076 above),
  and it is not sorted by property ID.

## See also

* [Properties and values](https://developer.m-files.com/APIs/gRPC-API/concepts/properties-and-values/index.md)
* [Objects and versions](https://developer.m-files.com/APIs/gRPC-API/concepts/objects-and-versions/index.md)
* [Download a file](https://developer.m-files.com/APIs/gRPC-API/guides/download-file/index.md)
